home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 May: Tool Chest / Dev.CD May 97 TC.toast / Sample Code / Files / StdFile / StdFile.p < prev    next >
Encoding:
Text File  |  1994-11-18  |  101.9 KB  |  3,104 lines  |  [TEXT/MPS ]

  1. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  2. #
  3. #         Apple Macintosh Developer Technical Support
  4. #
  5. #         Standard File Sample Application
  6. #
  7. #         StdFile
  8. #
  9. #         StdFile.p        -        Pascal Source
  10. #
  11. #         Copyright © 1989 Apple Computer, Inc.
  12. #         All rights reserved.
  13. #
  14. #    Versions:    
  15. #                1.00                04/89
  16. #                2.00                05/90
  17. #                2.01                06/92
  18. #
  19. #    Components:
  20. #                StdFile.p            April 1, 1989
  21. #                StdFile.h            April 1, 1988
  22. #                StdFile.r            April 1, 1988
  23. #                PStdFile.make        April 1, 1988
  24. #
  25. # ----------
  26. # OBJECTIVES
  27. # ----------
  28. # This program attempts to demonstrate the following techniques:
  29. #
  30. # - Normal use of SFGetFile and SFPutFile
  31. # - Normal use of SFPGetFile and SFPPutFile.  This includes use of Custom
  32. #   Dialogs with handling of extra items through the implementation of a
  33. #   Standard File Dialog Hook.
  34. #     - First time initialization
  35. #     - Extra Simple buttons (Disory/ThisDir/Options)
  36. #     - Radio buttons (file format, types of files to show)
  37. #     - Aliasing click on some buttons to clicks on other buttons
  38. #     - Regenerating the list of files displayed
  39. # - Creating a full pathname from the reply record (using Working Directories
  40. #   or DirID)
  41. # - Selecting a directory (ala MPW's "GetFileName -d")
  42. # - Simple file filter (checks file type)
  43. # - Complex file filter (looking inside the file)
  44. # - Adding and deleting and extra List Manager lists.  This is shown for both
  45. #   SFGetFile and SFPutFile.
  46. # - Select multiple files by adding a second list to the Dialog Box.
  47. # - Setting the starting Directory/Volume
  48. # - Describe pending update event clogging
  49. # - Ask the user to select a file, and then remember where it is the next
  50. #   time the application is launched.
  51. #
  52. # --------------------------
  53. # Standard File Generalities
  54. # --------------------------
  55. #
  56. # The prompt string of SFGetFile is ignored in all calls.  As noted on page
  57. # I-523 of Inside Mac, it is there for historical reasons only.  The
  58. # Standard File package is built around a modal dialog window.  Standard
  59. # File repeatedly calls ModalDialog waiting for user events.  It is
  60. # possible for an application to hook into the dialog event loop by using
  61. # the following methods.
  62. #
  63. # File Filter
  64. # -----------
  65. # The file filter is called to give the application opportunity to filter
  66. # out files that is wishes not to be shown in the dialog.  This file filter
  67. # is called before displaying the dialog, which is also before the Dialog
  68. # Hook or Dialog Filter are called.  If this function returns TRUE, this
  69. # means the file should not be shown in the list.  In other words the file
  70. # is to be filtered from the user.
  71. #
  72. # Dialog Hook
  73. # -----------
  74. # The dialog hook is called after Standard File calls ModalDialog.  The
  75. # hook is a function that requires that an item number be passed back from
  76. # it.  Normally, this is the same item number that was passed to us, but
  77. # not necessarily.  For instance, we could return values that cause the
  78. # file names to be re-drawn or have the whole event ignored.  Before the
  79. # dialog is drawn, our dialog hook gets called with a -1.  This gives us
  80. # the opportunity to change things like Button titles, etc.  It's in
  81. # important to note that Standard File seems to ignore the item returned by
  82. # the dialog hook when it is called the first time.  The dialog hook will
  83. # also be passed a fake item of 100, which is used to mean a nullEvent.
  84. # This can be used as an idle event for the dialog hook.
  85. #
  86. # Dialog Filter
  87. # -------------
  88. # This is used in the SFPGetFile and SFPPutFile routines.  It is for the
  89. # advanced versions of calling Standard File for programmers that desire a
  90. # custom dialog window.  The dialog filter is the same filter procedure
  91. # used by the Dialog Manager.  In the call to ModalDialog, you can supply a
  92. # filter.  The dialog filter used by Standard File is passed to
  93. # ModalDialog.  This filter is called by the Dialog Manager after it calls
  94. # GetNextEvent.  This gives the filter the first opportunity to handle the
  95. # event.  If the dialog filter returns TRUE, this tells the Dialog Manager
  96. # that the filter has handled the event.  You will use the dialog filter to
  97. # handle items you've added to your custom dialog.
  98. #
  99. #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  100.  
  101. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  102. PROGRAM SFSample;
  103.  
  104. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  105. USES
  106.     Types, QuickDraw, ToolUtils, Events, Controls, Windows, Dialogs,
  107.     Menus, Devices, SegLoad, Files, Traps, Fonts, Resources, Memory,
  108.     Packages, Lists, DiskInit, Script, LowMem, CursorCtl, Errors, FixMath,
  109.     GestaltEqu, StandardFile;
  110.  
  111. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  112. CONST
  113.  
  114. {k prefix for constants, r for resources, m for menu, i for menu item,
  115. and s for string resource item}
  116.  
  117. {Resource ID's for some alerts}
  118.     rAboutAlert            = 1000;             {the about dialog}
  119.     rExitAlert            = 1001;             {emergency exit user alert}
  120.     rUserAlert            = 1002;             {error message user alert}
  121.     rFInfoAlert            = 1003;                {display file info alert}
  122.     rShowSelectionAlert    = 1004;
  123.  
  124. {Resource ID's for my Standard File Dialogs}
  125.     rListGetDLOG        = 2000;
  126.     kFileListItem        = 11;                {Item number for list of files}
  127.     kMultiOpenItem        = 12;                {Item number of multiple Open files}
  128.     kRemoveItem            = 13;                {Item number of Remove button}
  129.  
  130.     rSFPGetFileDLOG     = 2001;
  131.     kTextButton            = 11;                {DoNormalPGet dialog items}
  132.     kAppButton            = 12;
  133.  
  134.     rSFPPutFileDLOG     = 2002;
  135.     kSFPPutNormalBtn    = 9;                {SFPPutFile with a Dialog Hook}
  136.     kSFPPutTextBtn        = 10;
  137.     kSFPPutFreeSpace    = 11;
  138.  
  139.     rGetDirectoryDLOG    = 2003;
  140.     kGetDirButton        = 11;                {DoGetDirectory dialog items}
  141.     kGetDirNowButton     = 12;
  142.     kGetDirMessage        = 13;
  143.  
  144.     rPutListsFileDLOG    = 2004;
  145.     kListItem            = 9;                {ListPut dialog items}
  146.     kReplaceItem        = 2;
  147.  
  148.     rOptionsDLOG        = 2005;
  149.     kOptionsButton        = 9;                {Put Options dialog items}
  150.     kFormatString        = 10;
  151.  
  152.     rOptionsSubDLOG     = 2006;
  153.     kDefaultFormat        = 3;                {Put Sub-Options dialog items}
  154.     kFrameItem            = 9;
  155.  
  156.     rGetIdleUpdates        = 2007;
  157.     kTimeItem            = 11;                {DoIdleUpdates dialog items}
  158.     kSizeItem            = 12;
  159.  
  160.     kExistingFileALRT    = -3996;            {"Replace file?" dialog ID from System file}
  161.  
  162. {menu resources; m for menu and i for menu item}
  163.     rMenuBar            = 1000;                {Menu resources}
  164.  
  165.     mApple                = 128;                {indexes for Apple menu items}
  166.     iAboutMe            = 1;
  167.  
  168.     mFile                = 129;                {indexes for File menu items}
  169.     iQuit                = 12;
  170.  
  171.     mEdit                = 130;                {indexes for Edit menu items}
  172.     iUndo                = 1;
  173.     iCut                = 3;
  174.     iCopy                = 4;
  175.     iPaste                = 5;
  176.     iClear                = 6;
  177.  
  178.     mSFExamples            = 131;                {indexes for Examples menu items}
  179.     iNormalGet            = 1;
  180.     iNormalPGet         = 2;
  181.     iFileFilter         = 3;
  182.     iGetDirectory        = 4;
  183.     iMultiFile            = 5;
  184.  
  185.     iNormalPut            = 7;
  186.     iNormalPPut         = 8;
  187.     iForceDirectory     = 9;
  188.     iPutListsFile        = 10;
  189.     iPutOptions         = 11;
  190.  
  191.     iIdleUpdates        = 13;
  192.     iRememberFile        = 14;
  193.  
  194. {Miscellaneous Strings from Str# resource}
  195.     rStrMisc            = 1000;
  196.     sKFree                = 1;                {K free}
  197.     sSelectFolder        = 2;                {Select a Folder}
  198.     sSelectedCancel        = 3;                {The Cancel button.}
  199.     sSaveAsMsg            = 4;                {Save As…}
  200.     sTempFName            = 5;                {suffix of temporary file name}
  201.     sFileSize            = 6;                {K on disk}
  202.  
  203.     rStrList            = 1001;                {Used to create a List Manager list}
  204.  
  205.     rErrStrings         = 1002;             {error String Str# ID}
  206.     sStandardErr        = 1;                {An error has occurred.}
  207.     sMemErr             = 2;                {A Memory Manager error has occurred.}
  208.     sResErr             = 3;                {A Resource Manager error has occurred.}
  209.     sLowMemory            = 4;                {Memory is too low to continue...}
  210.     sNoMenus            = 5;                {Could not find application's menu...}
  211.     sFileSystem            = 6;                {A File System error has occurred.}
  212.     sLostFile            = 7;                {The file to be remembered has been lost}
  213.     sOldAUX                = 8;                {Requires A/UX version 2.0 or later.}
  214.     sNoHFS                = 9;                {Requires a System that supports HFS}
  215.  
  216. {Useful Stadard File constants}
  217.     kSFTopLeft            = $00500050;        {topLeft corner of Standard File dialog}
  218.     kReDrawList            = 101;                {causes the file list to be recalculated}
  219.     kShowAllFiles        = -1;                {show all files in the StdFile list}
  220.     kShowIt             = FALSE;            {FALSE means I do not filter out...}
  221.     kNoSelection        = 0;                {reply.fType is NIL for no selection}
  222.     kNullModalEvt        = 100;                {Item number for null event from ModalDialog}
  223.     kNoItem                = 0;                {no item, ignore it}
  224.     kFirstTime            = -1;                {the first time our hook it's passed a -1}
  225.  
  226. {Useful File Manager constants}
  227.     kFSAsynch             = TRUE;                {asynchronous File Manager call}
  228.     kInvisibleBit        = 14;                {bit set in ioFlFndrInfo.fdFlags if invisible}
  229.     kFolderBit            = 4;                {bit set in ioFlAttrib for a folder}
  230.  
  231. {misc. application constants}
  232.     kScrollbarWidth     = 16;                {kScrollBarWidth can be used in
  233.                                              calculating values for control
  234.                                              positioning and sizing.}
  235.     kScrollbarAdjust    = kScrollbarWidth - 1;
  236.  
  237.     kListFrameInset     = 1;                {inset rectangle adjustment for list frame}
  238.     kCntlActivate        = 0;                {enabled control’s hilite state}
  239.     kCntlDeactivate     = $FF;                {disabled control’s hilite state}
  240.     kCntlOn             = 1;                {control’s value when truned on}
  241.     kCntlOff            = 0;                {control’s value when truned off}
  242.     kButtonFrameSize    = 3;                {button frame’s pen size}
  243.     kButtonFrameInset    = - 4;                {inset rectangle adjustment around button}
  244.     kSysEnvironsVersion = 1;                {Version of the SysEnvRec I understand.}
  245.     kBroughtToFront        = 3;                {Number of get event calls to pull us forward}
  246.     kWantSeconds        = TRUE;             {IUTimeString flag for using seconds}
  247.     kMinSpace            = 32 * 1024;        {minimum available memory I allow in heap}
  248.     kMinAUXVersion        = $200;                {minimum version of A/UX I'll run with}
  249.     kDefaultAUXVersion    = $100;                {assumed version 1.x.x of A/UX}
  250.     rUpdateWindow        = 1000;                {WIND resource for updates}
  251.     rMemorizedFile        = 1000;                {FILE resource of file to remember}
  252.     kNumberOfMasters    = 1;                {number of master pointer blocks}
  253.     rAppSignature        = 'sc18';            {app signature/creator ID}
  254.  
  255. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  256. TYPE
  257.  
  258. {This record is enough to specify a file before rebooting.  File names were limited
  259. to 63 chars with MFS, but HFS limits names to 31 chars}
  260.  
  261.     FileSpec            = RECORD
  262.         vRefNum:        INTEGER;
  263.         parID:            LONGINT;
  264.         name:            Str63;
  265.     END;
  266.     FileSpecList        = ARRAY[1..1000] OF FileSpec;
  267.     FileSpecPtr            = ^FileSpecList;
  268.     FileSpecHdl            = ^FileSpecPtr;
  269.  
  270. {This record is enough to specify a file after rebooting.  This record is kept
  271. as a resource when the user selects a file.}
  272.  
  273.     FileDesc            = RECORD
  274.         volName:        Str27;                {volume names are limited to 27 chars}
  275.         parID:            LONGINT;
  276.         name:            Str63;                {file names are limited to 63 chars}
  277.     END;
  278.     FileDescPtr            = ^FileDesc;
  279.     FileDescHdl            = ^FileDescPtr;
  280.  
  281. {This record is used for the unsigned long integers which Pascal really doesn't handle}
  282.  
  283.     TwoIntsMakesALong    = RECORD            {build a signed LONGINT...}
  284.         CASE INTEGER OF                     {...from an unsigned INTEGER}
  285.             1:
  286.                 (long:    LONGINT);
  287.             2:
  288.                 (ints:    ARRAY [0..1] OF INTEGER);
  289.         END;
  290.  
  291. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  292. VAR
  293.  
  294. {gMac is used to hold the result of a SysEnvirons call. This makes it
  295.  convenient for any routine to check the environment. It is considered
  296.  global information, anyway.}
  297.  
  298.     gMac:                SysEnvRec;            {set up by Initialize}
  299.  
  300. {gInBackground is maintained by our osEvent handling routines. Any part of
  301.  the program can check it to find out if it is currently in the background.}
  302.  
  303.     gInBackground:         BOOLEAN;            {maintained by Initialize and EventLoop}
  304.  
  305. {This determines if we can call WaitNextEvent, which is always present for any
  306.  System 6.0x user but only possible for earilier systems running MultiFinder.}
  307.  
  308.     gHasWaitNextEvent:     BOOLEAN;            {set by Initialize}
  309.  
  310. {These next three globals are used to remember the application's location.
  311. This is the volume and the folder that hold the application at launch time.}
  312.  
  313.     gAppName:            Str255;                {application's name}
  314.     gAppParID:            LONGINT;            {dirID of application's folder}
  315.     gAppVRefNum:        INTEGER;            {volume containing application}
  316.  
  317. {Sometimes it is necessary to have a global SFReply record.}
  318.  
  319.     gReply:                SFReply;            {used in some SF samples}
  320.  
  321. {This is a flag used to signal a normal file format or not used in a couple examples.}
  322.  
  323.     gNormal:            BOOLEAN;
  324.  
  325. {This is a handle to an array of my FileSpecs.  It is accessed by a few routines, so
  326. it needed to be global.}
  327.  
  328.     gMultiFiles:        FileSpecHdl;        {array of files to open}
  329.  
  330. {This is a handle to a List Manager list.  It is accessed by a few routines, so
  331. it needed to be global.}
  332.  
  333.     gListHandle:        ListHandle;         {list use for multiple get files}
  334.  
  335. {This is the selected dirID that the user selected while in SFGet.}
  336.  
  337.     gMyCurDir:            LONGINT;            {for SetDirectory sample}
  338.  
  339. {This is a string set by a few of the examples used to determine which file format
  340. the user wanted to save the file as.}
  341.  
  342.     gPutFormat:            Str255;                {string of file format}
  343.  
  344.     gLsBkUp:            LONGINT;
  345.  
  346.  
  347. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  348. {$S Initialize}
  349.  
  350. FUNCTION NumToolboxTraps: INTEGER;
  351.  
  352. {InitGraf is always implemented (trap $A86E). If the trap table is big
  353.  enough, trap $AA6E will always point to either Unimplemented or some other
  354.  trap, but will never be the same as InitGraf. Thus, you can check the size
  355.  of the trap table by asking if the address of trap $A86E is the same as $AA6E.}
  356.  
  357. BEGIN
  358.     IF NGetTrapAddress(_InitGraf, ToolTrap) = NGetTrapAddress($AA6E, ToolTrap) THEN
  359.         NumToolboxTraps := $200
  360.     ELSE
  361.         NumToolboxTraps := $400;
  362. END;
  363.  
  364. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  365. {$S Initialize}
  366.  
  367. FUNCTION GetTrapType(theTrap: INTEGER): TrapType;
  368.  
  369. {Determines the type of a trap based on its trap number. If bit 11 is clear,
  370.  then it is an OS trap. Otherwise, it is a Toolbox trap.}
  371.  
  372. BEGIN
  373.     { OS traps start with A0, Tool with A8 or AA. }
  374.     IF BAND(theTrap, $0800) = 0 THEN            {per Darin}
  375.         GetTrapType := OSTrap
  376.     ELSE
  377.         GetTrapType := ToolTrap;
  378. END;
  379.  
  380. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  381. {$S Initialize}
  382.  
  383. FUNCTION TrapExists(theTrap: INTEGER): BOOLEAN;
  384.  
  385. { Check to see if a given trap is implemented. This is only used by the
  386.   Initialize routine in this program, so we put it in the Initialize segment. }
  387.  
  388.     VAR
  389.         theTrapType:        TrapType;
  390.  
  391.     BEGIN
  392.         theTrapType := GetTrapType(theTrap);
  393.         IF (theTrapType = ToolTrap) THEN BEGIN
  394.             theTrap := BAND(theTrap, $07FF);
  395.             IF theTrap >= NumToolboxTraps THEN
  396.                 theTrap := _Unimplemented;
  397.         END;
  398.  
  399.         TrapExists := NGetTrapAddress(_Unimplemented, ToolTrap) <> NGetTrapAddress(theTrap,
  400.                       theTrapType);
  401.     END;                                            { TrapExists }
  402.  
  403. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  404. {$S Initialize}
  405.  
  406. FUNCTION GetAUXVersion: INTEGER;
  407.  
  408. {The glue code for Gestalt will return appropriate values if the
  409. system doesn't support the trap.  If an error is returned from Gestalt,
  410. then either we weren't running on A/UX, or the Gestalt trap
  411. was unable to fulfill the request so we revert to using low memory
  412. globals instead.  This will work for A/UX version 1.x.  All other errors
  413. are ignored (implies A/UX not present).
  414.  
  415. NOTE: We right shift AUXversion by 8 bits to get major version number
  416. contained in the high byte, since we're not concerned with the minor
  417. version.  Anything better than 2.0 is good enough for me.}
  418.  
  419. CONST
  420.     kAUXbit                = 9;                    {bit set in LMGetHWCfgFlags for A/UX}
  421.     kShiftHighByte        = 8;                    {shift 8 bits to the right}
  422.  
  423. VAR
  424.     AUXversion:            LONGINT;
  425.     err:                INTEGER;
  426.  
  427. BEGIN
  428.     err:= Gestalt(gestaltAUXVersion, AUXVersion);
  429.  
  430.     IF (err <> noErr) THEN BEGIN
  431.         IF BTst(LMGetHWCfgFlags, kAUXbit) THEN
  432.             AUXversion:= kDefaultAUXVersion        {A/UX is running, default version}
  433.         ELSE
  434.             AUXVersion:= 0;                        {less than 1 means no version}
  435.     END;
  436.     GetAUXVersion:= BSR(AUXversion, kShiftHighByte);
  437. END;
  438.  
  439. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  440. {$S Main}
  441.  
  442. {The following set of routines are used to access a couple of low memory
  443. globals that are necessary when extending Standard File.  One example is
  444. trying to get the current directory while in a file filter.  These routines
  445. were used to bottleneck all the low memory usage.  If the system one day
  446. supports them with a trap call, then we can easily update these routines.}
  447.  
  448. FUNCTION GetSFCurDir: LONGINT;
  449.  
  450. BEGIN
  451.     GetSFCurDir:= LMGetCurDirStore;
  452. END;
  453.  
  454. PROCEDURE SetSFCurDir(dirID: LONGINT);
  455.  
  456. BEGIN
  457.     LMSetCurDirStore(dirID);
  458. END;
  459.  
  460. FUNCTION GetSFVRefNum: INTEGER;
  461.  
  462. BEGIN
  463.     GetSFVRefNum:= -LMGetSFSaveDisk;
  464. END;
  465.  
  466. PROCEDURE SetSFVRefNum(vRefNum: INTEGER);
  467.  
  468. BEGIN
  469.     LMSetSFSaveDisk(-vRefNum);
  470. END;
  471.  
  472. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  473. {$S Main}
  474.  
  475. FUNCTION GetKFreeSpace(vRefNum: INTEGER): LONGINT;
  476.  
  477. {Return the amount of free space on the volume in KBytes. This builds a
  478.   LONGINT based on an unsigned INTEGER, which looks like a negitive value to
  479.   Pascal.  -1 is return as the size if there is an error.}
  480.  
  481. VAR
  482.     pb:                 HParamBlockRec;
  483.     convert:            TwoIntsMakesALong;
  484.     err:                OSErr;
  485.  
  486. BEGIN
  487.     WITH pb DO BEGIN                            {set up the block for PBHGetVInfo}
  488.         ioNamePtr := NIL;                        {we don't care about the name}
  489.         ioVRefNum := vRefNum;
  490.         ioVolIndex := 0;                        {use ioVRefNum only}
  491.     END;                                        {with}
  492.     err := PBHGetVInfo(@pb, FALSE);
  493.  
  494.     IF (err = noErr) THEN BEGIN
  495.         convert.ints[0] := 0;
  496.         convert.ints[1] := pb.ioVFrBlk;
  497.         GetKFreeSpace := (convert.long * pb.ioVAlBlkSiz) DIV 1024;
  498.     END
  499.     ELSE                                        {we couldn't get free space size!}
  500.         GetKFreeSpace := - 1;
  501. END;
  502.  
  503. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  504. {$S Main}
  505.  
  506. FUNCTION FailLowMemory(memRequest: LONGINT): BOOLEAN;
  507.  
  508. {Call PurgeSpace and see if the requested amount of memory exists in the
  509. heap including a minimal amount of heap space.  Also, if my grow zone’s
  510. reserve has been release, that’s considered a failure.  I don’t perform
  511. any purging here.  The Memory Manager will do this if it needs the space.
  512. This routine can be called with a memRequest = 0.  This checks if the heap
  513. space is getting critical.}
  514.  
  515. VAR
  516.     total, contig:        LONGINT;
  517.  
  518. BEGIN
  519.     PurgeSpace(total, contig);
  520.     FailLowMemory:= total < (memRequest + kMinSpace);
  521. END;
  522.  
  523. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  524. {$S Main}
  525.  
  526. FUNCTION GetWTitleHeight(variant: INTEGER): INTEGER;
  527.  
  528. {Try and determine the window’s title bar height.  This isn’t easy,
  529.  especially if the window is invisible.  We all know how the standard Apple
  530.  System WDEF works, at least at this date we do.  I assume that method as
  531.  shown below.  I could do what MacApp does with the Function CallWDefProc.
  532.  If the user has installed a replacement for the System WDEF, then it’s
  533.  their problem to deal with.  My method will work as long as Apple doesn’t
  534.  change how the WDEF in System 6.0 calculates the title bar height.  This
  535.  will allow my application to work on international Macs that have a larger
  536.  system font than Chicago.  I check the window’s variant code for one that
  537.  includes a title bar.  I will have to change this routine to adjust for
  538.  the modal-moveable window type, which hasn’t been defined yet.
  539.  
  540.  In this routine, I violate my rule about not using the GetPort, SetPort,
  541.  SetPort sequence mentioned at the start of the file. Mostly, I do this
  542.  because it’s not all that apparent that a routine called GetWTitleHeight
  543.  will change the port, so I make sure that it doesn’t.}
  544.  
  545. VAR
  546.     info:                FontInfo;
  547.     curGraf,
  548.     wMgrPort:        GrafPtr;
  549.     wTitleHeight:    INTEGER;
  550.  
  551. BEGIN
  552.     IF variant IN [documentProc, noGrowDocProc,
  553.                         zoomDocProc, zoomNoGrow, rDocProc] THEN BEGIN
  554.         GetPort(curGraf);
  555.         GetWMgrPort(wMgrPort);                        {I need to know the font...}
  556.         SetPort(wMgrPort);                            {info in the System’s port}
  557.         GetFontInfo(info);
  558.         SetPort(curGraf);                            {restore current port}
  559.         WITH info DO
  560.             wTitleHeight:= ascent + descent + leading + 2;
  561.         IF wTitleHeight < 19 THEN
  562.             wTitleHeight:= 19;                        {the title is always at least 19}
  563.         GetWTitleHeight:= wTitleHeight;
  564.     END ELSE
  565.         GetWTitleHeight:= 0;                        {other window types have no title}
  566. END; {GetWTitleHeight}
  567.  
  568. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  569. {$S Main}
  570.  
  571. PROCEDURE CenterWindowRect(variant: INTEGER; VAR theRect: Rect);
  572.  
  573. {Given a window’s portRect, this routine will center the rectangle on the
  574.  main device within the desktop region.  This excludes the menu bar area.
  575.  It follows the Apple Human Interface Guidelines for where to place a
  576.  window centered on the screen. That is, 1/3th of the desktop shows above
  577.  the window and 2/3ths below it.  It returns a new rectangle set to the
  578.  centered position.  The top and left of this rectangle can be used with
  579.  MoveWindow.  This routine only considers the main device.  CenterWindowRect
  580.  could take a GDevice as a parameter to center the VAR rect with.  This
  581.  would also allow Alerts or dialogs that are closely associated with a
  582.  document window to be centered relative to the monitor that contains that
  583.  document.  This is also one of the Human Interface Guidelines.  MacApp 2.0
  584.  has a utility CalcScreenRect that you can borrow from to include this.
  585.  
  586.  WARNING: This routine may move or purge memory.}
  587.  
  588. VAR
  589.     rectSize:         Point;
  590.     wTitleHeight:    INTEGER;
  591.  
  592. BEGIN
  593.     wTitleHeight:= GetWTitleHeight(variant);        {get title height}
  594.     WITH theRect DO BEGIN                            {get size of rect}
  595.         SetPt(rectSize, right, bottom + wTitleHeight);    {include it in size}
  596.         SubPt(topLeft, rectSize);
  597.     END;
  598.     WITH qd.screenBits.bounds DO BEGIN                    {1/3th below menubar}
  599.         theRect.top:= ((bottom - top - LMGetMBarHeight - rectSize.v) DIV 3)
  600.                           + LMGetMBarHeight;
  601.         theRect.left:= ((right - left) - rectSize.h) DIV 2;    {centered horz}
  602.     END;
  603.     WITH theRect DO BEGIN                            {return adjusted rect}
  604.         SetPt(botRight, left, top);
  605.         AddPt(rectSize, botRight);
  606.         top:= top + wTitleHeight;                    {remove title height from rect}
  607.     END;
  608. END; {CenterWindowRect}
  609.  
  610. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  611. {$S Main}
  612.  
  613. FUNCTION CenteredAlert(alertID: INTEGER): INTEGER;
  614.  
  615. {Given an Alert ID, this routine will center the alert’s rectangle before
  616.  showing it on the main screen.  This follows the Apple Human Interface
  617.  Guidelines for where to place a centered window on the screen.  If the
  618.  Alert is closely associated with another window, considerations should be
  619.  given to what the window is located.  If this other window is on a screen
  620.  other then the main monitor, then it would be best to center the Alert on
  621.  that other monitor.}
  622.  
  623. VAR
  624.     alertHandle:    AlertTHndl;
  625.     alertRect:        Rect;
  626.     itemHit:            INTEGER;
  627.  
  628. BEGIN
  629.     alertHandle:= AlertTHndl(Get1Resource('ALRT', alertID));
  630.     IF alertHandle <> NIL THEN BEGIN
  631.         HNoPurge(Handle(alertHandle));
  632.         alertRect:= alertHandle^^.boundsRect;
  633.         CenterWindowRect(dBoxProc, alertRect);
  634.         alertHandle^^.boundsRect:= alertRect;
  635.         HPurge(Handle(alertHandle));
  636.     END;
  637.     CenteredAlert:= Alert(alertID, NIL);
  638. END; {CenteredAlert}
  639.  
  640. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  641. {$S Main}
  642.  
  643. PROCEDURE EmergencyExit(message: INTEGER);
  644.  
  645. {Display an alert that tells the user an error occurred, then exit the
  646. program.  This routine is used as an ultimate bail-out for serious errors
  647. that prohibit the continuation of the application.  Errors that do not
  648. require the termination of the application are handled with AlertUser.
  649. Error checking and reporting has a place even in the simplest application.
  650.  
  651. BUG NOTE: GetIndString will return a bogus String if the index is not
  652. positive.}
  653.  
  654. VAR
  655.     msg1:                Str255;
  656.     theItem:            INTEGER;
  657.  
  658. BEGIN
  659.     SetCursor(qd.arrow);
  660.     IF message > 0 THEN
  661.         GetIndString(msg1, rErrStrings, message)
  662.     ELSE
  663.         msg1:= '';                                {in case there is no message}
  664.     ParamText(msg1, '', '', '');
  665.     theItem:= CenteredAlert(rExitAlert);
  666.     ExitToShell;                                {we're out of here}
  667. END;
  668.  
  669. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  670. {$S Main}
  671.  
  672. PROCEDURE SetRadioButton(dlgPtr: DialogPtr; itemNo: INTEGER; state: INTEGER);
  673.  
  674. {Handy routine for setting the value of a radio button. Given a dialog
  675.   pointer, and item number, and a state, this routine will take care of the
  676.   rest.}
  677.  
  678. VAR
  679.     iKind:                INTEGER;
  680.     iHandle:            Handle;
  681.     iRect:                Rect;
  682.  
  683. BEGIN
  684.     GetDialogItem(dlgPtr, itemNo, iKind, iHandle, iRect);
  685.     SetControlValue(ControlHandle(iHandle), state);
  686. END;
  687.  
  688. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  689. {$S Main}
  690.  
  691. PROCEDURE PositionTwoRects(outerRect: Rect; VAR innerRect: Rect; horzRatio, vertRatio: Fixed);
  692.  
  693. {Given two rectangles, this routine positions the second within the first one
  694.   so that the it maintains the spacing specified by the horzRatio and vertRatio
  695.   parameters. In other words, to center an inner rectangle hoizontally, but
  696.   have its center be 1/3 from the top of the outer rectangle, call this
  697.   routine with horzRatio = FixRatio (1, 2), vertRatio = FixRatio (1, 3).  We use
  698.   Fixed rather than floating point because Fixed has plenty of accuracy and is very fast. }
  699.  
  700. VAR
  701.     outerRectHeight:    INTEGER;
  702.     outerRectWidth:     INTEGER;
  703.     innerRectHeight:    INTEGER;
  704.     innerRectWidth:     INTEGER;
  705.     yLocation:            INTEGER;
  706.     xLocation:            INTEGER;
  707.  
  708. BEGIN
  709.     outerRectHeight := outerRect.bottom - outerRect.top;
  710.     outerRectWidth := outerRect.right - outerRect.left;
  711.  
  712.     innerRectHeight := innerRect.bottom - innerRect.top;
  713.     innerRectWidth := innerRect.right - innerRect.left;
  714.  
  715.     yLocation := Fix2Long (FixMul (Long2Fix (outerRectHeight - innerRectHeight), vertRatio))
  716.         + outerRect.top;
  717.     xLocation := Fix2Long (FixMul (Long2Fix (outerRectWidth - innerRectWidth), horzRatio))
  718.         + outerRect.left;
  719.     WITH innerRect DO BEGIN
  720.         top := yLocation;
  721.         left := xLocation;
  722.         bottom := yLocation + innerRectHeight;
  723.         right := xLocation + innerRectWidth;
  724.     END;
  725. END;
  726.  
  727. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  728. {$S Main}
  729.  
  730. PROCEDURE OutlineButton(button: UNIV ControlHandle);
  731.  
  732. {Given any control handle, this will draw an outline around it.  This is
  733.  used for the default button of a window.  The extra nice feature here is
  734.  that I’ll erase the outline for buttons that are inactive.  Seems like
  735.  there should be a Toolbox call for getting a control’s hilite state.
  736.  Since there isn’t, I have to look into the control record myself.    This
  737.  should be called for update and activate events.
  738.  
  739.  The method for determining the oval diameters for the roundrect is a
  740.  little different than that recommended by Inside Mac.    IM I-407 suggests
  741.  that you use a hardcoded (16,16) for the diameters. However, this only
  742.  looks good for small roundrects. For larger ones, the outline doesn’t
  743.  follow the inner roundrect because the CDEF for simply buttons doesn’t
  744.  use (16,16). Instead, it uses half the height of the button as the
  745.  diameter. By using this formula, too, our outlines look better.
  746.  
  747.  WARNING: This will set the current port to the control’s window.}
  748.  
  749. VAR
  750.     theRect:            Rect;
  751.     curPen:             PenState;
  752.     buttonOval:         INTEGER;
  753.  
  754. BEGIN
  755.     IF button <> NIL THEN BEGIN
  756.         SetPort(button^^.contrlOwner);
  757.         GetPenState(curPen);
  758.         PenNormal;
  759.         theRect := button^^.contrlRect;
  760.         InsetRect(theRect, kButtonFrameInset, kButtonFrameInset);
  761.         buttonOval := (theRect.bottom - theRect.top) DIV 2;
  762.         IF (button^^.contrlHilite = kCntlActivate) THEN
  763.             PenPat(qd.black)
  764.         ELSE
  765.             PenPat(qd.gray);
  766.         PenSize(kButtonFrameSize, kButtonFrameSize);
  767.         FrameRoundRect(theRect, buttonOval, buttonOval);
  768.         SetPenState(curPen);
  769.     END;
  770. END;
  771.  
  772. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  773. {$S Main}
  774.  
  775. PROCEDURE DoAbout;
  776.  
  777. VAR
  778.     apNameHndl:         StringHandle;
  779.     curVersion:         VersRecHndl;
  780.     apName:             Str255;
  781.     verNum:             Str255;
  782.     itemHit:            INTEGER;
  783.  
  784. BEGIN
  785.     curVersion := VersRecHndl(Get1Resource('vers', 1));
  786.     IF curVersion <> NIL THEN BEGIN
  787.         verNum := StringPtr(ORD(@curVersion^^.shortVersion) + ORD(curVersion^^.
  788.                             shortVersion[0]) + 1)^ { get long version string }
  789.     END ELSE
  790.         verNum := '????';                        {at least initialize it}
  791.  
  792.     ParamText(gAppName, verNum, '', '');
  793.     itemHit := CenteredAlert(rAboutAlert);
  794. END;
  795.  
  796. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  797. {$S Main}
  798.  
  799. PROCEDURE AlertUser(error: INTEGER; messageID: INTEGER);
  800.  
  801. {Display an alert to inform the user of an error.  MessageID acts as an
  802. index into a Str# resource of error messages.    This will attempt to
  803. replace the error number with a String if the sound is a Sound Manager
  804. error.
  805.  
  806. BUG NOTE: GetIndString will return a bogus String if the index is not
  807. positive.}
  808.  
  809. VAR
  810.     msg1, msg2:         Str255;
  811.     theItem:            INTEGER;
  812.  
  813. BEGIN
  814.     IF messageID > 0 THEN
  815.         GetIndString(msg1, rErrStrings, messageID)
  816.     ELSE
  817.         msg1:= '';                                {in case there is no message}
  818.     IF error <> noErr THEN
  819.         NumToString(error, msg2)
  820.     ELSE
  821.         msg2:= '';
  822.     ParamText(msg1, msg2, '', '');
  823.     theItem:= CenteredAlert(rUserAlert);
  824. END;
  825.  
  826. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  827. {$S Main}
  828.  
  829. FUNCTION MyHGetVol(volName: StringPtr; VAR vRefNum: INTEGER; VAR dirID: LONGINT): OSErr;
  830.  
  831. {BUG NOTE: The high level call, HGetVol, should do this dirty work for us.
  832. Unfortunately it is returning the ioVRefNum which Inside Mac warns may be
  833. a working directory.  The field ioWDVRefNum will always be the real vRefNum.}
  834.  
  835. VAR
  836.     myWDBRec:            WDPBRec;
  837.  
  838. BEGIN
  839.     myWDBRec.ioCompletion:= NIL;
  840.     myWDBRec.ioNamePtr:= NIL;
  841.     MyHGetVol:= PBHGetVol(@myWDBRec, kFSAsynch);
  842.     vRefNum:= myWDBRec.ioWDVRefNum;                {the real vRefNum}
  843.     dirID:= myWDBRec.ioWDDirID;
  844. END;
  845.  
  846. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  847. {$S Main}
  848.  
  849. FUNCTION GetTempFileName: Str63;
  850.  
  851. {Build a name for a temporary file by adding a suffix (contained in a
  852. resource) to the application's name.}
  853.  
  854. VAR
  855.     tempStr:            Str255;
  856.  
  857. BEGIN
  858.     GetIndString(tempStr, rStrMisc, sTempFName);{build file name}
  859.     GetTempFileName:= CONCAT(gAppName, tempStr);
  860. END;
  861.  
  862. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  863. {$S Main}
  864.  
  865. PROCEDURE Terminate;
  866.  
  867. {Clean up the application and exit.  Delete the temporary file.}
  868.  
  869. VAR
  870.     err:                OSErr;
  871.  
  872. BEGIN
  873.     err:= HDelete(gAppVRefNum, gAppParID, GetTempFileName);
  874.     ExitToShell;                                {exit if no cancellation}
  875. END;                                            {Terminate}
  876.  
  877. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  878. {$S Main}
  879.  
  880. PROCEDURE DrawItemFrame(dlgPtr: DialogPtr; itemNo: INTEGER);
  881.  
  882. {Draws a frame around the dialog item.}
  883.  
  884. VAR
  885.     iRect:                Rect;
  886.     ps:                 PenState;
  887.     iHandle:            Handle;
  888.     iKind:                INTEGER;
  889.  
  890. BEGIN
  891.     GetDialogItem(dlgPtr, itemNo, iKind, iHandle, iRect);
  892.     GetPenState(ps);
  893.     PenSize(1, 1);
  894.     FrameRect(iRect);
  895.     SetPenState(ps);
  896. END;
  897.  
  898. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  899. {$S Main}
  900.  
  901. FUNCTION PathNameFromDirID(DirID: LONGINT; vRefnum: INTEGER;
  902.                             VAR fullPathName: Str255): OSErr;
  903.  
  904. {Given a DirID and real vRefnum, this routine will create and return the
  905. full pathname that it corresponds to. It does this by calling
  906. PBGetCatInfo for the given directory, and finding out its name and the
  907. DirID of its parent. It the performs the same operation on the parent,
  908. sticking its name onto the beginning of the first directory. This whole
  909. process is carried out until we have processed the root directory
  910. (identified with a DirID of fsRtDirID.
  911.  
  912. SPECIAL NOTE: This routine is once check for A/UX and would add the "/"
  913. delimiter.  A/UX 2.0 supports the ":" just like HFS, and 2.0 is the
  914. officially supported version.  So, we no longer need to special case the
  915. code.  An important comment that was originally in this routine should be
  916. considered again.
  917.  
  918. HOWEVER, BECAUSE OF THIS DEPENDENCY ON THE IDIOSYNCRASIES OF FILE
  919. SYSTEMS, GENERATING FULL PATHNAMES FOR OTHER THAN DISPLAY PURPOSES IS
  920. DISCOURAGED; IT'S CHANGED IN THE PAST WHEN A/UX WAS IMPLEMENTED, AND IT
  921. MAY CHANGE AGAIN IN THE FUTURE IF SUPPORT FOR OTHER FILE SYSTEMS SUCH AS
  922. PRODOS, MS-DOS, OR OS/2 ARE ADDED.
  923.  
  924. The full path name cannot exceed 255 chars.  If this happens, an error
  925. bdNamErr is returned.  So be careful out there!}
  926.  
  927. VAR
  928.     dirName:            Str255;
  929.     fullName:            Str255;
  930.     myCInfo:            CInfoPBRec;
  931.     err:                OSErr;
  932.  
  933. BEGIN
  934.     err:= noErr;
  935.     fullName:= '';
  936.     dirName:= '';
  937.     myCInfo.ioNamePtr:= @dirName;
  938.     myCInfo.ioDrParID:= DirID;
  939.  
  940.     REPEAT
  941.         WITH myCInfo DO BEGIN
  942.             ioVRefNum:= vRefnum;
  943.             ioFDirIndex:= -1;                    {-1 means use ioDrDirID}
  944.             ioDrDirID:= ioDrParID;
  945.         END;
  946.         err:= PBGetCatInfo(@myCInfo, NOT kFSAsynch);
  947.         IF err = noErr THEN BEGIN
  948.             dirName:= CONCAT(dirName, ':');
  949.             IF LENGTH(dirName) + LENGTH(fullName) > 255 THEN
  950.                 err:= bdNamErr                    {too big to eat!}
  951.             ELSE
  952.                 fullName:= CONCAT(dirName, fullName);
  953.         END;
  954.     UNTIL (myCInfo.ioDrDirID = fsRtDirID) | (err <> noErr);
  955.  
  956.     fullPathName:= fullName;
  957.     PathNameFromDirID:= err;
  958. END;
  959.  
  960. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  961. {$S Main}
  962.  
  963. FUNCTION PathNameFromWD(wdRefNum: LONGINT; VAR pathName: Str255): OSErr;
  964.  
  965. {Given an HFS working directory, this routine returns the full pathname
  966. that it corresponds to. It does this by calling PBGetWDInfo to get the
  967. real VRefNum and DirID of the working directory. It then calls
  968. PathNameFromDirID, and returns its result.
  969.  
  970. BUG NOTE:  The special case portion of this routine that worked around an
  971. U/UX 1.1 bug has been removed.  This application only runs with A/UX 2.0.
  972. The following comment has been left here for historical purposes only.
  973.  
  974. PBGetWDInfo has a bug under A/UX 1.1.  If vRefNum is a real vRefNum and
  975. not a wdRefNum, then it returns garbage.  Since A/UX has only one volume
  976. (in the Macintosh sense) and only one root directory, this can occur only
  977. when a file has been selected in the root directory (/). So we look for
  978. this and hardcode the DirID and vRefNum.
  979.  
  980. SPECIAL NOTE: Make sure you have read and understood Tech Note 238.}
  981.  
  982. VAR
  983.     dirID:                LONGINT;
  984.     procID:                LONGINT;
  985.     vRefNum:            INTEGER;
  986.     err:                OSErr;
  987.  
  988. BEGIN
  989.     err:= GetWDInfo(wdRefNum, vRefNum, dirID, procID);
  990.     IF err = noErr THEN
  991.         err:= PathNameFromDirID(dirID, vRefNum, pathName);
  992.     PathNameFromWD:= err;
  993. END;
  994.  
  995. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  996. {$S Main}
  997.  
  998. PROCEDURE ShowSelection(name, msg: Str255);
  999.  
  1000. {This routine shows an Alert of the selected item (name) and an optional
  1001. message (msg) that is used as the file's type.}
  1002.  
  1003. VAR
  1004.     theItem:            INTEGER;
  1005.  
  1006. BEGIN
  1007.     ParamText(name, msg, '', '');
  1008.     theItem:= CenteredAlert(rShowSelectionAlert);
  1009. END;
  1010.  
  1011. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1012. {$S Main}
  1013.  
  1014. PROCEDURE ShowCancelled;
  1015.  
  1016. {This simply calls up the alert showing the user selected the cancel
  1017. button of Standard File.}
  1018.  
  1019. VAR
  1020.     tStr:                Str255;
  1021.     theItem:            INTEGER;
  1022.  
  1023. BEGIN
  1024.     GetIndString(tStr, rStrMisc, sSelectedCancel);
  1025.     ParamText(tStr, '', '', '');
  1026.     theItem:= CenteredAlert(rShowSelectionAlert);
  1027. END;
  1028.  
  1029. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1030. {$S Main}
  1031.  
  1032. FUNCTION ShowFoundFile(fName: Str63; parID: LONGINT; vRefNum: INTEGER): OSErr;
  1033.  
  1034. {This alert shows the file name and the modification date to prove that the
  1035. file can be located by the given parameters.}
  1036.  
  1037. VAR
  1038.     msg1:                Str255;
  1039.     fHPBRec:            HParamBlockRec;
  1040.     theItem:            INTEGER;
  1041.     err:                OSErr;
  1042.  
  1043. BEGIN
  1044.     WITH fHPBRec DO BEGIN
  1045.         ioCompletion:= NIL;
  1046.         ioNamePtr:= @fName;
  1047.         ioFDirIndex:= 0;
  1048.         ioDirID:= parID;
  1049.         ioVRefNum:= vRefNum;
  1050.     END;
  1051.     err:= PBHGetFInfo(@fHPBRec, NOT kFSAsynch);
  1052.     IF err = noErr THEN BEGIN
  1053.         IUDateString(fHPBRec.ioFlMdDat, longDate, msg1);
  1054.         ParamText(fName, msg1, '', '');
  1055.         theItem:= CenteredAlert(rFInfoAlert);
  1056.     END;
  1057.     ShowFoundFile:= err;
  1058. END;
  1059.  
  1060. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1061. {$S Main}
  1062.  
  1063. PROCEDURE UIPFileList(theWindow: WindowPtr; item: INTEGER);
  1064.  
  1065. {Dialog user item procedure to draw the multi-file list.  The multi-file
  1066. list item is requrest to be drawn by the Dialog Manager.  A handle to the
  1067. list must be stored in the global variable "gListHandle" because the
  1068. Dialog Manager isn’t kind enough to let us pass a little information to
  1069. our user-item procedures in the good computer science way, dagnabbit. The
  1070. "theWindow" parameter is actually a pointer to our custom SFGetFile
  1071. dialog. The "item" parameter specifies the item number to be drawn.
  1072. Since only one item in our dialog uses UIPFileList, we don’t really have
  1073. to check to see if the item number is in fact the multi-file list, but we
  1074. might as well so that people think we’re some cool programmers who take
  1075. care to check everything.
  1076.  
  1077. Check to be absolutely sure we’re talking about the file list item before
  1078. we do anything to it. This alert shows the file name and the modification
  1079. date to prove that the file can be located by the given parameters.}
  1080.  
  1081. BEGIN
  1082.     IF item = kFileListItem THEN BEGIN
  1083.         LUpdate(theWindow^.visRgn, gListHandle);{Draw visible part of list}
  1084.         DrawItemFrame(theWindow, kFileListItem); {...and frame it}
  1085.     END;
  1086. END;
  1087.  
  1088. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1089. {$S Main}
  1090.  
  1091. PROCEDURE CreateFGetList(dlgPtr: DialogPtr; VAR listRect: Rect;
  1092.                             VAR listH: ListHandle);
  1093.  
  1094. {Build a three column list.  Set the column's width to cover the entire
  1095. list's visible rectangle.  This is done by adjusting the cellSize.  This
  1096. keeps the other two columns hidden from the user.  I also do not allow a
  1097. horizontal scrolling to prevent the user from viewing the hidden data.
  1098. It's a sick hack.  I really don't like this idea, nor would I encourage
  1099. anyone else to do this.  Sorta, "Do as I say, not as I do."  I also
  1100. adjust the list rectangle for best scrolling.  This is something that few
  1101. people do. They must be hard coding a size.  The adjusted rectangle is
  1102. returned so the caller will know the new size.}
  1103.  
  1104. VAR
  1105.     lBounds:             Rect;                    {Rectangle of list data}
  1106.     cellSize:            Point;                    {size of a cell}
  1107.     info:                FontInfo;
  1108.     newHeight:            INTEGER;                {adjustement to list rect size}
  1109.  
  1110. BEGIN
  1111.     InsetRect(listRect, kListFrameInset, kListFrameInset);
  1112.     listRect.right:= listRect.right - kScrollbarAdjust;
  1113.     GetFontInfo(info);
  1114.     cellSize.h:= listRect.right - listRect.left;
  1115.     cellSize.v:= info.ascent + info.descent + info.leading;
  1116.     newHeight:= cellSize.v;
  1117.     WHILE (newHeight + cellSize.v) < (listRect.bottom - listRect.top) DO
  1118.         newHeight:= newHeight + cellSize.v;
  1119.     listRect.bottom:= listRect.top + newHeight;
  1120.     SetRect(lBounds, 0, 0, 3, 0);                {three columns in list}
  1121.     listH:= LNew(listRect,                         {Location of list in window}
  1122.                     lBounds,                    {Initial size of array}
  1123.                     cellSize,                     {Cell size (0 = default)}
  1124.                     0,                            {Resource ID of LDEF}
  1125.                     dlgPtr,                        {Pointer to our Std File dialog}
  1126.                     TRUE,                         {needs to be drawn?}
  1127.                     FALSE,                        {grow box?}
  1128.                     FALSE,                        {horizontal scrolling?}
  1129.                     TRUE);                        {vertical scrolling?}
  1130.     InsetRect(listRect, -kListFrameInset, -kListFrameInset);
  1131.     listRect.right:= listRect.right + kScrollbarAdjust;
  1132. END;
  1133.  
  1134. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1135. {$S Main}
  1136.  
  1137. PROCEDURE DeselectAllCells(listH: ListHandle);
  1138.  
  1139. VAR
  1140.     listCell:            Cell;
  1141.     done:                BOOLEAN;
  1142.  
  1143. BEGIN
  1144.     listCell:= Point(0);
  1145.     done:= FALSE;
  1146.     REPEAT
  1147.         IF LGetSelect(TRUE, listCell, listH) THEN
  1148.             LSetSelect(FALSE, listCell, listH)
  1149.         ELSE
  1150.             done:= TRUE;
  1151.     UNTIL done;
  1152. END;
  1153.  
  1154. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1155. {$S Main}
  1156.  
  1157. PROCEDURE AddFItem(name: Str255; curDir: LONGINT; vRefNum: INTEGER;
  1158.                         listH: ListHandle);
  1159.  
  1160. {This takes the given information and adds it to the list.  First thing to
  1161. do is check for the file to have already been added to the list.  If it
  1162. has, then we only have to show the user that the item is already there.
  1163. Before we eixt, we select just the current cell and the scroll it into view.}
  1164.  
  1165. VAR
  1166.     listCell:            Cell;                    {File list cell}
  1167.     itemDirID:            LONGINT;
  1168.     itemVRefNum:        INTEGER;
  1169.     dataLength:            INTEGER;
  1170.     done:                BOOLEAN;
  1171.  
  1172. BEGIN
  1173.     listCell:= Point(0);
  1174.     done:= FALSE;
  1175.     REPEAT
  1176.         IF LSearch(@name[1], LENGTH(name), NIL, listCell, listH) THEN BEGIN
  1177.             listCell.h:= 1;
  1178.             dataLength:= SizeOf(itemDirID);
  1179.             LGetCell(@itemDirID, dataLength, listCell, listH);
  1180.             listCell.h:= 2;
  1181.             dataLength:= SizeOf(itemVRefNum);
  1182.             LGetCell(@itemVRefNum, dataLength, listCell, listH);
  1183.             IF (curDir = itemDirID) & (vRefNum = itemVRefNum) THEN BEGIN
  1184.                 done:= TRUE;                    {found the existing item}
  1185.             END;
  1186.         END
  1187.         ELSE BEGIN
  1188.             listCell.h:= 0;
  1189.             listCell.v:= LAddRow(1, listH^^.dataBounds.bottom, listH);
  1190.             LSetCell(@gReply.fName[1], LENGTH(name), listCell, listH);
  1191.             listCell.h:= 1;
  1192.             LSetCell(@curDir, SizeOf(curDir), listCell, listH);
  1193.             listCell.h:= 2;
  1194.             LSetCell(@vRefNum, SizeOf(vRefNum), listCell, listH);
  1195.             LSetSelect(TRUE, listCell, listH);
  1196.             done:= TRUE;
  1197.         END;
  1198.     UNTIL done;
  1199.     DeselectAllCells(listH);
  1200.     listCell.h:= 0;                                {first column, current row}
  1201.     LSetSelect(TRUE, listCell, listH);
  1202.     LAutoScroll(listH);
  1203. END;
  1204.  
  1205. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1206. {$S Main}
  1207.  
  1208. PROCEDURE GetFItems(listH: ListHandle);
  1209.  
  1210. {We need to copy all of the files from the global list and store these into
  1211. a global array of file descriptions.}
  1212.  
  1213. VAR
  1214.     theCell:            Point;
  1215.     numOfFiles:            INTEGER;
  1216.     dataLength:            INTEGER;
  1217.     index:                INTEGER;
  1218.  
  1219. BEGIN
  1220.     numOfFiles:= listH^^.dataBounds.bottom;
  1221.     gMultiFiles:= FileSpecHdl(NewHandle(SizeOf(FileDesc) * numOfFiles));
  1222.     MoveHHI(Handle(gMultiFiles));        {helps to reduce fragmentation}
  1223.     HLock(Handle(gMultiFiles));
  1224.     FOR index:= 1 TO numOfFiles DO BEGIN
  1225.         dataLength:= SizeOf(gMultiFiles^^[index].vRefNum);
  1226.         SetPt(theCell, 2, index - 1);
  1227.         LGetCell(@gMultiFiles^^[index].vRefNum, dataLength, theCell, listH);
  1228.         dataLength:= SizeOf(gMultiFiles^^[index].parID);
  1229.         SetPt(theCell, 1, index - 1);
  1230.         LGetCell(@gMultiFiles^^[index].parID, dataLength, theCell, listH);
  1231.         dataLength:= SizeOf(gMultiFiles^^[index].name);
  1232.         SetPt(theCell, 0, index - 1);
  1233.         LGetCell(@gMultiFiles^^[index].name[1], dataLength, theCell, listH);
  1234.         gMultiFiles^^[index].name[0]:= CHR(dataLength);
  1235.     END;
  1236.     HUnlock(Handle(gMultiFiles));
  1237. END;
  1238.  
  1239. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1240. {$S Main}
  1241.  
  1242. PROCEDURE RemoveFItems(listH: ListHandle);
  1243.  
  1244. {Delete all selected files from the multi-file list.  Don’t want all that
  1245. flickerin’, so turn off drawing.  Keep deleting selected rows until no
  1246. selected rows are left.  Finally, turn on drawing and erase the list.
  1247. It’ll get drawn on the next update event.  This takes the given
  1248. information and adds it to the list.}
  1249.  
  1250. VAR
  1251.     listRect:            Rect;                    {Rectangle for list}
  1252.     listCell:            Cell;                    {File list cell}
  1253.  
  1254. BEGIN
  1255.     LSetDrawingMode(FALSE, listH);
  1256.     listCell:= Point(0);
  1257.     WHILE LGetSelect(TRUE, listCell, listH) DO BEGIN
  1258.         LDelRow(1, listCell.v, listH);
  1259.         listCell:= Point(0);
  1260.     END;
  1261.     listCell:= Point(0);
  1262.     LSetSelect(TRUE, listCell, listH);
  1263.     LAutoScroll(listH);
  1264.     listRect:= listH^^.rView;
  1265.     LSetDrawingMode(TRUE, listH);
  1266.     InvalRect(listRect);
  1267.     EraseRect(listRect);
  1268. END;
  1269.  
  1270. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1271. {$S Main}
  1272.  
  1273. PROCEDURE CreateFPutList(dlgPtr: DialogPtr; VAR listRect: Rect;
  1274.                             VAR listH: ListHandle; VAR cellStr: Str255);
  1275.  
  1276. {This creates a list used to hold a number of file types.  The selected
  1277. item is used to determine which format the file should be saved as.  The
  1278. list rectangle is adjusted to the best scrolling size, and the adjusted
  1279. size is returned to the caller.  It's also important to select the first
  1280. item in the list as a default file format and that only one item can be
  1281. selected.}
  1282.  
  1283. VAR
  1284.     entry:                Str255;
  1285.     lBounds:            Rect;
  1286.     newSize:            Point;
  1287.     theCell:            Point;
  1288.     newRow:             INTEGER;
  1289.     dataLen:            INTEGER;
  1290.  
  1291. BEGIN
  1292.     InsetRect(listRect, kListFrameInset, kListFrameInset);
  1293.     listRect.right:= listRect.right - kScrollbarAdjust;
  1294.     SetRect(lBounds, 0, 0, 1, 0);                 {one dimentional list}
  1295.     listH:= LNew(listRect,                        {position in window}
  1296.                     lBounds,                     {initial size of array}
  1297.                     Point(0),                    {cell size (0 = default)}
  1298.                     0,                            {resource ID of LDEF}
  1299.                     dlgPtr,                        {window pointer}
  1300.                     TRUE,                        {drawit}
  1301.                     FALSE,                        {has grow}
  1302.                     FALSE,                        {scrollHoriz}
  1303.                     TRUE);                        {scrollVert}
  1304.     newSize:= listH^^.cellSize;                    {get the size of one cell}
  1305.     WHILE (newSize.v + listH^^.cellSize.v) < (listRect.bottom - listRect.top) DO
  1306.         newSize.v:= newSize.v + listH^^.cellSize.v;
  1307.     LSize(newSize.h, newSize.v, listH);            {adjust for best scrolling}
  1308.     listH^^.selFlags:= lOnlyOne;                {single selections only}
  1309.  
  1310.     theCell:= Point(0);
  1311.     REPEAT
  1312.         GetIndString(entry, rStrList, theCell.v + 1);
  1313.         IF (entry <> '') THEN BEGIN
  1314.             newRow:= LAddRow(1, listH^^.databounds.bottom, listH);
  1315.             LSetCell(@entry[1], LENGTH(entry), theCell, listH);
  1316.         END;
  1317.         theCell.v:= theCell.v + 1;
  1318.     UNTIL (entry = '');
  1319.     theCell:= Point(0);
  1320.     LSetSelect(TRUE, theCell, listH);
  1321.     datalen:= SizeOf(cellStr);
  1322.     LGetCell(@cellStr[1], datalen, theCell, listH);
  1323.     cellStr[0]:= CHR(dataLen);
  1324.  
  1325.     listRect.bottom:= listRect.top + newSize.v;
  1326.     InsetRect(listRect, -kListFrameInset, -kListFrameInset);
  1327.     listRect.right:= listRect.right + kScrollbarAdjust;
  1328. END;
  1329.  
  1330. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1331. {$S Main}
  1332.  
  1333. PROCEDURE DisposeFPutList(listH: ListHandle; VAR cellStr: Str255);
  1334.  
  1335. {Just get rid of the list, but before we do that get the currently selected
  1336. cell's data which is the user's choice of file format.}
  1337.  
  1338. VAR
  1339.     theCell:            Point;
  1340.     dataLen:            INTEGER;
  1341.  
  1342. BEGIN
  1343.     SetPt(theCell, 0, 0);
  1344.     IF LGetSelect(TRUE, theCell, listH) THEN BEGIN
  1345.         datalen:= SizeOf(cellStr);
  1346.         LGetCell(@cellStr[1], datalen, theCell, listH);
  1347.         cellStr[0]:= CHR(dataLen);                {length byte}
  1348.     END;
  1349.     LDispose(listH);
  1350. END;
  1351.  
  1352. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1353. {Simple SFGetFile example}
  1354. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1355.  
  1356. {Simplest form of SFGetFile.  This routine puts up a GetFile dialog with a
  1357. request to show all files.  When this is done, a check is made to see
  1358. which of the Save or Cancel buttons were pressed.  If the Save button was
  1359. pressed, ShowSelection is called to display which file was selected.  If
  1360. the Cancel button was pressed, a dialog is shown saying so.
  1361.  
  1362. A full path name is shown of the selected file.  This is done as an
  1363. example.  Generally full path names should be avoided.  If you wanted to
  1364. remember the location of a file, such as the next time the application is
  1365. launch, you should remember the volume name, directory ID, and file name.
  1366. As an example of how to do this, refer to the routine DoRememberFile.}
  1367.  
  1368. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1369. {$S Main}
  1370.  
  1371. PROCEDURE ShowPath;
  1372.  
  1373. VAR
  1374.     pathStr:            Str255;
  1375.     typeList:            SFTypeList;
  1376.     reply:                SFReply;
  1377.     err:                OSErr;
  1378.  
  1379. BEGIN
  1380.     SFGetFile(Point(kSFTopLeft),                {location}
  1381.               '',                                {prompt String}
  1382.               NIL,                                {file filter}
  1383.               kShowAllFiles,                    {numtypes}
  1384.               typeList,                         {array to types to show}
  1385.               NIL,                                {dialog hook}
  1386.               reply);                            {record for returned values}
  1387.     IF reply.good THEN BEGIN
  1388.         err:= PathNameFromWD(reply.vRefnum, pathStr);
  1389.         IF err = noErr THEN BEGIN
  1390.             IF LENGTH(pathStr) + LENGTH(reply.fName) > 255 THEN
  1391.                 AlertUser(bdNamErr, sFileSystem)
  1392.             ELSE
  1393.                 ShowSelection(CONCAT(pathStr, reply.fName), '');
  1394.         END
  1395.         ELSE
  1396.             AlertUser(err, sFileSystem);
  1397.     END
  1398.     ELSE
  1399.         ShowCancelled;
  1400. END;
  1401.  
  1402.  
  1403. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1404. {SFPGetFile with Dialog Hook and Simple File Filter}
  1405. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1406.  
  1407. {Depending on the value of the global variable "gNormal", this
  1408. demonstrations shows either TEXT files or APPL files.  The value of
  1409. "gNormal" is determined by the states of two radio buttons that are added
  1410. to the dialog box.  Our dialog hook routine is used to initialize the radio
  1411. buttons and handle hits on them.  When there is a hit on a radio button,
  1412. "gNormal" is set to either TRUE or FALSE, and a special command is sent
  1413. back to Standard File telling it to regenerate the list of file names it
  1414. is displaying.  This sample consists of 3 parts:
  1415.  
  1416. DoNormalPGet
  1417. ------------
  1418. This routine initializes a variable for our file filter, and then calls
  1419. SFPGetFile with pointers to two other routines and a resource number for
  1420. a special dialog box with extra items in it.
  1421.  
  1422. SimpleFFilter
  1423. ----------------
  1424. Specified in our SFPGetFile call to be called to specify whether or not a
  1425. file should be displayed.  If the global variable 'gNormal' is TRUE, then
  1426. all text are displayed.  Otherwise, Applications will be displayed.
  1427.  
  1428. MySFGetDlgHook
  1429. ----------------
  1430. A routine that is called to handle hits on the non-standard items in our
  1431. dialog box.  The dialog hook is also used to perform some special
  1432. initialization on the items in the dialog box.  Standard file does this by
  1433. calling this routine with a bogus 'item' number of -1.  When we get this
  1434. number, we initialize our radio buttons, and change the text in the Open
  1435. button.
  1436.  
  1437. The radio buttons are used to determine what files will appear in the
  1438. files list.  It does this by appropriately setting the 'gNormal' variable
  1439. for SimpleFFilter, and then telling Standard File that the file list
  1440. needs to be regenerated by passing back 101 as the function result.}
  1441.  
  1442. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1443. {$S Main}
  1444.  
  1445. FUNCTION MySFGetDlgHook(MySFitem: INTEGER; dlgPtr: DialogPtr): INTEGER;
  1446.  
  1447. BEGIN
  1448.     MySFGetDlgHook:= MySFitem;
  1449.     CASE MySFitem OF
  1450.  
  1451.         kFirstTime: BEGIN
  1452.             SetRadioButton(dlgPtr, kAppButton, kCntlOff);
  1453.             SetRadioButton(dlgPtr, kTextButton, kCntlOn);
  1454.         END;
  1455.  
  1456.         kTextButton: BEGIN
  1457.             IF NOT gNormal THEN BEGIN
  1458.                 SetRadioButton(dlgPtr, kAppButton, kCntlOff);
  1459.                 SetRadioButton(dlgPtr, kTextButton, kCntlOn);
  1460.                 gNormal:= TRUE;
  1461.                 MySFGetDlgHook:= kReDrawList;
  1462.             END;
  1463.         END;
  1464.  
  1465.         kAppButton: BEGIN
  1466.             IF gNormal THEN BEGIN
  1467.                 SetRadioButton(dlgPtr, kTextButton, kCntlOff);
  1468.                 SetRadioButton(dlgPtr, kAppButton, kCntlOn);
  1469.                 gNormal:= FALSE;
  1470.                 MySFGetDlgHook:= kReDrawList;
  1471.             END;
  1472.         END;
  1473.  
  1474.     END;
  1475. END;
  1476.  
  1477.  
  1478. FUNCTION SimpleFFilter(p: ParmBlkPtr): BOOLEAN;
  1479.  
  1480. BEGIN
  1481.     SimpleFFilter:= NOT kShowIt;                {Don't show it -- default}
  1482.     WITH p^.ioFlFndrInfo DO
  1483.         IF gNormal THEN BEGIN
  1484.             IF fdType = 'TEXT' THEN
  1485.                 SimpleFFilter:= kShowIt;
  1486.         END
  1487.         ELSE
  1488.             IF fdType = 'APPL' THEN
  1489.                 SimpleFFilter:= kShowIt;
  1490. END;
  1491.  
  1492.  
  1493. PROCEDURE DoNormalPGet;
  1494.  
  1495. VAR
  1496.     typeList:            SFTypeList;
  1497.     reply:                SFReply;
  1498.     err:                OSErr;
  1499.     ffUPP:                FileFilterUPP;
  1500.     dhUPP:                DlgHookUPP;
  1501.  
  1502. BEGIN
  1503.     gNormal:= TRUE;
  1504.     
  1505.     ffUPP := NewFileFilterProc(@SimpleFFilter);
  1506.     dhUPP := NewDlgHookProc(@MySFGetDlgHook);
  1507.     SFPGetFile(Point(kSFTopLeft),                {location}
  1508.                 '',                                {vestigial String}
  1509.                 ffUPP,                            {file filter}
  1510.                 kShowAllFiles,                    {numtypes}
  1511.                 typeList,                        {array to types to show}
  1512.                 dhUPP,                            {dialog hook}
  1513.                 reply,                            {record for returned values}
  1514.                 rSFPGetFileDLOG,                 {ID of Custom Dialog}
  1515.                 NIL);                            {ModalDialog filterProc}
  1516.     DisposeRoutineDescriptor(dhUPP);
  1517.     DisposeRoutineDescriptor(ffUPP);
  1518.     IF reply.good THEN
  1519.         ShowSelection(reply.fName, '')
  1520.     ELSE
  1521.         ShowCancelled;
  1522. END;
  1523.  
  1524.  
  1525. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1526. {SFGetFile with a complex File Filter}
  1527. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1528.  
  1529. {Sample of a less trivial file filter.  This routine is responsible for
  1530. displaying only files that have 'ALRT' resources.  It shows how to
  1531. correctly look inside a file for qualifying information, and demonstrates
  1532. how to get around a particular quirk when attempting multiple access to
  1533. the resource fork.
  1534.  
  1535. DoFFilter
  1536. ---------
  1537. Calls SFGetFile and specifies the File Filter.
  1538.  
  1539. ComplexFFilter
  1540. --------------
  1541. This routine used to do skanky things like looking at a low memory global
  1542. to determine of a new resource file map was opened or not.  Even more
  1543. nastiness was the it created a working directory.  Well, this was all too
  1544. sick and a completely different approach is now taken.  Opening a
  1545. resource file as read-only will always create a new map.  This should
  1546. generally be avoided, but here I'm opening it and then immediately
  1547. closing it without allowing anything else to access the file.
  1548.  
  1549. Opening resource forks can be tricky if it's already open.  It would be
  1550. very bad to use a resource fork that is already open by another
  1551. application.  The problem being that the Resource Manager doesn't deal
  1552. with multiple users.  Read Tech notes #116 and #185.  There's also
  1553. another problem.  If a resource fork is opened by two applications and
  1554. one closes the file then the entire resource fork may closed out from
  1555. underneath the other application.  I do, however, want to show the user
  1556. files that contain ALRT resources even if the file is currently open.  I
  1557. use HOpenResFile with read only permission, which will give me a unique
  1558. resource reference.  I look for an ALRT resource and then immediately
  1559. close the file.  Do not use a resource file opened with read only
  1560. permission because another application could come along and start writing
  1561. to it.  Another reason to use HOpenResFile  is to avoid a necessary
  1562. working directory.  I can use the DirID kept updated by Standard File.
  1563. Performing this search on each file is time consuming so I show a
  1564. spinning cursor to show the user I'm working.    Opening a resource fork
  1565. may load resources mark preload.  To avoid this, I call SetResLoad to
  1566. FALSE.  Read Tech Note #203 for other reasons not to play with resources.}
  1567.  
  1568.  
  1569. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1570. {$S Main}
  1571.  
  1572. FUNCTION ComplexFFilter(p: ParmBlkPtr): BOOLEAN;
  1573.  
  1574. {SPECIAL NOTE: The ParamBlockRec is a relocatable object in the heap.  The
  1575. file name is one of the fields in this handle.  Because of this we need
  1576. to copy the fields that we use to a local variable to avoid deferenced
  1577. handle problems.}
  1578.  
  1579. VAR
  1580.     fileName:            Str255;
  1581.     oldTicks:            LONGINT;
  1582.     resRef, curRes:     INTEGER;
  1583.     vRefNum:            INTEGER;
  1584.  
  1585. BEGIN
  1586.     oldTicks:= TickCount;
  1587.     ComplexFFilter:= NOT kShowIt;                {don’t show anything until I say so}
  1588.     fileName:= p^.ioNamePtr^;                    {must save name}
  1589.     vRefNum:= p^.ioVRefNum;                        {must save volume (or WD) reference}
  1590.     curRes:= CurResFile;
  1591.     SetResLoad(FALSE);
  1592.     resRef:= HOpenResFile(vRefNum, GetSFCurDir, fileName, fsRdPerm);
  1593.     IF (resRef <> - 1) THEN BEGIN
  1594.         UseResFile(resRef);
  1595.         IF Count1Resources('ALRT') > 0 THEN
  1596.             ComplexFFilter:= kShowIt;            {hey, we found an Alert in here}
  1597.         CloseResFile(resRef);
  1598.     END;                                        {restore everything}
  1599.     SetResLoad(TRUE);
  1600.     UseResFile(curRes);
  1601.     RotateCursor(TickCount - oldTicks);
  1602. END;
  1603.  
  1604.  
  1605. PROCEDURE DoFFilter;
  1606.  
  1607. VAR
  1608.     typeList:            SFTypeList;
  1609.     reply:                SFReply;
  1610.  
  1611. BEGIN
  1612.     SpinCursor(0);                                {get the spinning cursor ready}
  1613.     SFGetFile(Point(kSFTopLeft),                {location}
  1614.               '',                                {vestigial String}
  1615.               @ComplexFFilter,                    {file filter}
  1616.               kShowAllFiles,                    {numtypes}
  1617.               typeList,                         {array to types to show}
  1618.               NIL,                                {dialog hook}
  1619.               reply);                            {record for returned values}
  1620.     IF reply.good THEN
  1621.         ShowSelection(reply.fName, '')
  1622.     ELSE
  1623.         ShowCancelled;
  1624. END;
  1625.  
  1626.  
  1627. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1628. {DoGetDirectory with a Dialog Hook and File Filter}
  1629. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1630.  
  1631. {This demonstrates how to modify SFGetFile to allow you to select a
  1632. directory.  This mimics the "GetFileName -d" function of MPW.  As a matter
  1633. of fact, the DLOG and DITL used in this sample were taken directly out of
  1634. MPW.  There are 2 major additions in the dialog used in this sample:
  1635.  
  1636. 1. a Simple button that lets one select the directory that is currently
  1637.    highlighted in the list of directories,
  1638. 2. Simple button at the top of the dialog that lets the user select the
  1639.    directory that we are currently *IN*.
  1640.  
  1641. DoGetDirectory
  1642. --------------
  1643. Sets up the pointers to the Dialog Hook and File Filter.  Once the user
  1644. has selected a folder, we look at the global gMyCurDir which was set
  1645. within the Dialog Hook.  We take this DirID and call PBGetCatInfo to find
  1646. the name of this folder.  This is so we can display the name to the user.
  1647.  
  1648. MyGetDirDlgHook
  1649. ----------------
  1650. No new techniques are really used in this sample.  Hits on the two simple
  1651. buttons are handled by a dialog hook called GetDirDlgHook.  Depending on
  1652. which button is hit, we set the global variable gMyCurDir to either
  1653. gReply.fType (for the currently highlighted directory) or what Standard
  1654. File has set as the current directory.  We then simulate a hit on the Open
  1655. button so that Standard File will return to our application and the
  1656. FSReply.good will be TRUE.
  1657.  
  1658. While an item is selected, the gReply.fType will be set to a value.
  1659. gReply.fType is set to 0 when nothing is selected.  We test for this
  1660. during idle time (item = 100) and activate the Directory button
  1661. appropriately.  We also use the "Item = -1" feature to set up a prompt
  1662. String.
  1663.  
  1664. FolderFFilter
  1665. -------------
  1666. Normally, folders are ALWAYS shown, and aren't even passed to this file
  1667. filter for judgement.  Under such circumstances, it is only necessary to
  1668. blindly return TRUE (allow no files whatsoever).  However, Standard File
  1669. is not documented in such a manner, and this feature may not be TRUE in
  1670. the future.  Therefore, we DO check to see if the entry passed to us
  1671. describes a file or a directory.  This can be done by checking a bit
  1672. in the ioFlAttrib, which is set for folders.}
  1673.  
  1674. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1675. {$S Main}
  1676.  
  1677. FUNCTION FolderFFilter(p: ParmBlkPtr): BOOLEAN;
  1678.  
  1679. BEGIN
  1680.     FolderFFilter:= NOT BTst(p^.ioFlAttrib, kFolderBit);
  1681. END;
  1682.  
  1683.  
  1684. FUNCTION GetDirDlgHook(item: INTEGER; dlgPtr: DialogPtr): INTEGER;
  1685.  
  1686. VAR
  1687.     messageTitle:        Str255;
  1688.     iRect:                Rect;
  1689.     iHandle:            Handle;
  1690.     iKind:                INTEGER;
  1691.  
  1692. BEGIN
  1693.     GetDirDlgHook:= item;                        {default, return same item}
  1694.     CASE item OF
  1695.         kFirstTime: BEGIN                        {Read prompt String from resource}
  1696.             GetIndString(messageTitle, rStrMisc, sSelectFolder);
  1697.             GetDialogItem(dlgPtr, kGetDirMessage, iKind, iHandle, iRect);
  1698.             SetDialogItemText(iHandle, messageTitle);
  1699.         END;
  1700.  
  1701.         kGetDirButton: BEGIN
  1702.             IF LONGINT(gReply.fType) <> 0 THEN BEGIN
  1703.                 gMyCurDir:= LONGINT(gReply.fType);
  1704.                 GetDirDlgHook:= getOpen;            {simulate Open button}
  1705.             END;
  1706.         END;
  1707.  
  1708.         kGetDirNowButton: BEGIN
  1709.             gMyCurDir:= GetSFCurDir;
  1710.             GetDirDlgHook:= getOpen;                {simulate Open button}
  1711.         END;
  1712.  
  1713.         kNullModalEvt: BEGIN
  1714.             IF LONGINT(gReply.fType) = kNoSelection THEN BEGIN
  1715.                 GetDialogItem(dlgPtr, kGetDirButton, iKind, iHandle, iRect);
  1716.                 HiliteControl(ControlHandle(iHandle), kCntlDeactivate);
  1717.             END
  1718.             ELSE BEGIN
  1719.                 GetDialogItem(dlgPtr, kGetDirButton, iKind, iHandle, iRect);
  1720.                 HiliteControl(ControlHandle(iHandle), kCntlActivate);
  1721.             END;
  1722.         END;
  1723.  
  1724.     END;
  1725. END;
  1726.  
  1727.  
  1728. PROCEDURE DoGetDirectory;
  1729.  
  1730. VAR
  1731.     folderName:            Str31;                    {directories can be 31 chars}
  1732.     typeList:            SFTypeList;
  1733.     myCInfo:            CInfoPBRec;
  1734.     err:                OSErr;
  1735.     ffUPP:                FileFilterUPP;
  1736.     dhUPP:                DlgHookUPP;
  1737.  
  1738. BEGIN
  1739.     ffUPP := NewFileFilterProc(@FolderFFilter);
  1740.     dhUPP := NewDlgHookProc(@GetDirDlgHook);
  1741.     SFPGetFile(Point(kSFTopLeft),                {location}
  1742.                '',                                {vestigial String}
  1743.                ffUPP,                            {file filter}
  1744.                kShowAllFiles,                    {numtypes}
  1745.                typeList,                        {array to types to show}
  1746.                dhUPP,                            {Dialog hook}
  1747.                gReply,                            {record for returned values}
  1748.                rGetDirectoryDLOG,                {Resource ID dialog}
  1749.                NIL);                            {ModalDialog filter proc}
  1750.     DisposeRoutineDescriptor(dhUPP);
  1751.     DisposeRoutineDescriptor(ffUPP);
  1752.  
  1753.     IF gReply.good THEN BEGIN
  1754.         folderName:= '';                        {initialize string}
  1755.         WITH myCInfo DO BEGIN
  1756.             ioCompletion:= NIL;                    {no completion}
  1757.             ioNamePtr:= @folderName;            {VAR to string}
  1758.             ioFDirIndex:= -1;                    {-1 means use ioDrDirID}
  1759.             ioVRefNum:= GetSFVRefNum;             {this is the volume}
  1760.             ioDrDirID:= gMyCurDir;                {this is the directory}
  1761.         END;
  1762.         err:= PBGetCatInfo(@myCInfo, NOT kFSAsynch);
  1763.         IF err = noErr THEN
  1764.             ShowSelection(folderName, '')
  1765.         ELSE
  1766.             AlertUser(err, sFileSystem);
  1767.     END
  1768.     ELSE
  1769.         ShowCancelled;
  1770. END;
  1771.  
  1772.  
  1773. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1774. {DoMultiFile}
  1775. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1776.  
  1777. {This demonstrates how to get multiple files with one call to Standard
  1778. File.  It collects a set of files while in the SFGetFile dialog by using
  1779. of a secondary list.  This is similar to the method used by many of the
  1780. MPW tools.  The original version of this example was a failed experiment.
  1781. It attempted remember all of the files passed to the file filter, and
  1782. using those names to create a new, application governed file list that
  1783. will replace the one displayed by Standard File.  However, this
  1784. experiment seems doomed to failure, for the following reasons:
  1785.  
  1786. * Icons are not displayed next to the names of files.  This would require
  1787.   a custom LDEF.  The resulting presentation without the small icons was
  1788.   more disconcerting that I thought.
  1789. * The current directory button (the one displayed above the list of file
  1790.   names) disappears.  This seems due to the fact that the pop-up menu-like
  1791.   item is not actually a dialog item, and is somehow attached to the list
  1792.   of file names handled by Standard File.  Since I move that list off of the
  1793.   window so that I can display my own, the current directory button seems
  1794.   to go with it.
  1795. * Updating the list is difficult.  The normal sequence of events would
  1796.   desirably be: 1) have the file filter create a linked list of names to
  1797.   appear in the dialog, 2) insert those names into the List Manager list,
  1798.   and 3) display the list.  However, by following this outline, the list
  1799.   does not get updated, as the update region for that area is empty.  So, at
  1800.   some time, we need to invalidate the area that holds our list.  But there
  1801.   is no convenient place in which to do that.  We can't call InvalRect
  1802.   within our file filter routine, as we don't have the DialogPtr at the
  1803.   time (needed to get the bounding rectangle of the list item).  Neither can
  1804.   we call InvalRect within step 2, as we are between
  1805.   BeginUpdate/EndUpdates, and calling InvalRect would be useless.
  1806. * We can't display directories.  They are not offered to the judgement of
  1807.   our file filter, and hence cannot be added to our list.
  1808. * Key presses would have do be handled manually by our dialog hook.
  1809.  
  1810. Considering all of these problems, another possible solution would be to
  1811. do away with Standard File altogether and do your own non-Standard File.
  1812. You could show a dialog (maybe even a modeless dialog) and contained a
  1813. list of file in the selected folder.  This really isn't all too
  1814. difficult.  The only disadvantage is that it may not look familiar to the
  1815. user, and the MultiFinder hack to force the application's Open item
  1816. wouldn't work.  This process is documented in Tech Note 205.
  1817.  
  1818. DoMultiFile
  1819. -----------
  1820. Points to the Dialog Filter and Dialog Hook.  If the user selected the
  1821. Open button then there is a handle containing an array of files.  We
  1822. index through this array and show some information for each file proving
  1823. that we located the file.  Once all the files have been found, then
  1824. dispose of the handle.
  1825.  
  1826. ListGetDlgHook
  1827. --------------
  1828. This routine is at the dialog item level, rather than the event level.
  1829. Normally, double-clicking on a file name in the Standard File list
  1830. simulates a click in the Open button.  We want it to add an item to our
  1831. multi-file list instead, so we've turned the Open button into an Add
  1832. button and sabotaged it's functionality so that it doesn't dismiss the
  1833. Standard File dialog.  Then, we added our own Open button which tricks
  1834. the Standard File code into thinking the normal Open button was clicked,
  1835. and our dialog is dismissed.
  1836.  
  1837. If there was an event (item <> 100) then handle the activating of our
  1838. Open button and the Remove buttons.  Activate the Open button if there
  1839. are any files in our file list.  Activate the Remove button if there are
  1840. any selected files in our file list.  If the Open or Cancel buttons were
  1841. selected, then dispose of the list since the user has dismissed the
  1842. dialog.
  1843.  
  1844. FListDlgFilter
  1845. --------------
  1846. This routine's raison d'être is to handle clicks in our file list (that
  1847. is, OUR file list at the bottom of the dialog; not SF's file list at the
  1848. top of the dialog).  If FListDlgFilter weren't around, clicks in our file
  1849. list would be ignored.  First we check to see if the mouse was clicked
  1850. somewhere in the rectangle of our file list.  If it wasn't, we return
  1851. FALSE, and SF handles the event on it's own.  If it was, call LClick and
  1852. let the List Manager handle all the hilighting and scrolling.
  1853.  
  1854. If there was a mouse click in our file list item, then itemHit returns
  1855. the item number of our file list.  If FListDlgFilter found out that the
  1856. mouse click wasn't in our file list, then itemHit is unchanged and we
  1857. return FALSE which tells the Dialog Manager to figures out which item has
  1858. been affected by the latest event.}
  1859.  
  1860. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  1861. {$S Main}
  1862.  
  1863. FUNCTION FListDlgFilter(dlgPtr: DialogPtr; VAR evt: EventRecord;
  1864.                           VAR itemHit: INTEGER): Boolean;
  1865.  
  1866. VAR
  1867.     iRect:                Rect;
  1868.     clickPos:            Point;
  1869.     iHandle:             Handle;
  1870.     iKind:                INTEGER;
  1871.     ignore:                Boolean;
  1872.  
  1873. BEGIN
  1874.     FListDlgFilter:= FALSE;                        {initailize result}
  1875.     IF evt.what = mouseDown THEN BEGIN            {Only care about mouseDown in our list}
  1876.         clickPos:= evt.where;
  1877.         GlobalToLocal(clickPos);
  1878.         GetDialogItem(dlgPtr, kFileListItem, iKind, iHandle, iRect);
  1879.         IF PtInRect(clickPos, iRect) THEN BEGIN
  1880.             ignore:= LClick(clickPos, evt.modifiers, gListHandle);
  1881.             itemHit:= kFileListItem;
  1882.             FListDlgFilter:= TRUE;
  1883.         END;
  1884.     END;
  1885. END;
  1886.  
  1887.  
  1888. FUNCTION ListGetDlgHook(item: INTEGER; dlgPtr: DialogPtr): INTEGER;
  1889.  
  1890. VAR
  1891.     iRect:                Rect;
  1892.     iHandle:            Handle;
  1893.     iKind:                INTEGER;
  1894.     listCell:            Cell;                    {File list cell}
  1895.  
  1896. BEGIN
  1897.     ListGetDlgHook:= item;                        {default, except in special cases below}
  1898.     CASE item OF
  1899.  
  1900.         kFirstTime: BEGIN
  1901.             GetDialogItem(dlgPtr, kMultiOpenItem, iKind, iHandle, iRect);
  1902.             HiliteControl(ControlHandle(iHandle), kCntlDeactivate);
  1903.             GetDialogItem(dlgPtr, kRemoveItem, iKind, iHandle, iRect);
  1904.             HiliteControl(ControlHandle(iHandle), kCntlDeactivate);
  1905.             GetDialogItem(dlgPtr, kFileListItem, iKind, iHandle, iRect);
  1906.             CreateFGetList(dlgPtr, iRect, gListHandle);
  1907.             SetDialogItem(dlgPtr, kFileListItem, iKind, Handle(@UIPFileList), iRect);
  1908.         END; {kFirstTime}
  1909.  
  1910.         getOpen: BEGIN                            {old Open button means add a file}
  1911.             AddFItem(gReply.fName,GetSFCurDir, GetSFVRefNum, gListHandle);
  1912.             ListGetDlgHook:= kNoItem;            {replace item with no item}
  1913.         END;
  1914.  
  1915.         kMultiOpenItem: BEGIN                    {new Open button means get all files}
  1916.             GetFItems(gListHandle);
  1917.             ListGetDlgHook:= getOpen;            {replace item with real Open item}
  1918.         END;
  1919.  
  1920.         kRemoveItem:
  1921.             RemoveFItems(gListHandle);            {remove the selected item from list}
  1922.  
  1923.     END; {CASE}
  1924.  
  1925.     IF item <> kNullModalEvt THEN BEGIN
  1926.         GetDialogItem(dlgPtr, kMultiOpenItem, iKind, iHandle, iRect);
  1927.         WITH gListHandle^^ DO
  1928.             IF dataBounds.top <> dataBounds.bottom THEN
  1929.                 HiliteControl(ControlHandle(iHandle), kCntlActivate)
  1930.             ELSE
  1931.                 HiliteControl(ControlHandle(iHandle), kCntlDeactivate);
  1932.         listCell:= Point(0);
  1933.         GetDialogItem(dlgPtr, kRemoveItem, iKind, iHandle, iRect);
  1934.         IF LGetSelect(TRUE, listCell, gListHandle) THEN
  1935.             HiliteControl(ControlHandle(iHandle), kCntlActivate)
  1936.         ELSE
  1937.             HiliteControl(ControlHandle(iHandle), kCntlDeactivate);
  1938.     END;
  1939.  
  1940.     IF (item = kMultiOpenItem) | (item = getCancel) THEN
  1941.         LDispose (gListHandle);
  1942. END;
  1943.  
  1944.  
  1945. PROCEDURE DoMultiFile;
  1946.  
  1947. VAR
  1948.     msg1:                Str255;
  1949.     typeList:            SFTypeList;
  1950.     index:                INTEGER;
  1951.     numOfFiles:            INTEGER;
  1952.     fHPBRec:            HParamBlockRec;
  1953.     err:                OSErr;
  1954.     theItem:            INTEGER;
  1955.     dhUPP:                DlgHookUPP;
  1956.     mfUPP:                ModalFilterUPP;
  1957.  
  1958. BEGIN
  1959.     dhUPP := NewDlgHookProc(@ListGetDlgHook);
  1960.     mfUPP := NewModalFilterProc(@FListDlgFilter);
  1961.     SFPGetFile(Point(kSFTopLeft),                    {Location of SFDialog}
  1962.                '',                                    {Here’s that String thing.}
  1963.                NIL,                                 {File filter}
  1964.                kShowAllFiles,                        {Number of file types to display}
  1965.                typeList,                            {Array of file types to show}
  1966.                dhUPP,                                {Std File dialog hook}
  1967.                gReply,                                {Record for returned SF info}
  1968.                rListGetDLOG,                        {Resource ID of dialog}
  1969.                mfUPP);                                {ModalDialog filter proc}
  1970.     DisposeRoutineDescriptor(dhUPP);
  1971.     DisposeRoutineDescriptor(mfUPP);
  1972.  
  1973.     IF gReply.good THEN BEGIN
  1974.         numOfFiles:= GetHandleSize(Handle(gMultiFiles)) DIV SizeOf(FileDesc);
  1975.  
  1976.         HLock(Handle(gMultiFiles));
  1977.         FOR index:= 1 TO numOfFiles DO BEGIN
  1978.             WITH fHPBRec DO BEGIN                    {prepare a hParamBlock}
  1979.                 ioCompletion:= NIL;
  1980.                 ioNamePtr:= @gMultiFiles^^[index].name;
  1981.                 ioVRefNum:= gMultiFiles^^[index].vRefNum;
  1982.                 ioFDirIndex:= 0;
  1983.                 ioDirID:= gMultiFiles^^[index].parID;
  1984.             END;
  1985.             err:= PBHGetFInfo(@fHPBRec, NOT kFSAsynch); {fPBRec on stack, synch only}
  1986.  
  1987.             IF err = noErr THEN BEGIN
  1988.                 IUDateString(fHPBRec.ioFlMdDat, longDate, msg1);
  1989.                 ParamText(gMultiFiles^^[index].name, msg1, '', '');
  1990.                 theItem:= CenteredAlert(rFInfoAlert);
  1991.             END
  1992.             ELSE
  1993.                 AlertUser(err, sFileSystem);
  1994.         END;
  1995.         DisposeHandle(Handle(gMultiFiles));
  1996.     END
  1997.     ELSE
  1998.         ShowCancelled;
  1999. END;
  2000.  
  2001.  
  2002. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2003. {Typical SFPutFile example}
  2004. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2005.  
  2006. {Simplest form of SFPutFile. This routine puts up a PutFile dialog with a
  2007. prompt and suggested file name.}
  2008.  
  2009. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2010. {$S Main}
  2011.  
  2012. PROCEDURE DoNormalPut;
  2013.  
  2014. VAR
  2015.     msg:                Str255;                    {prompt String}
  2016.     reply:                SFReply;
  2017.  
  2018. BEGIN
  2019.     GetIndString(msg, rStrMisc, sSaveAsMsg);
  2020.     SFPutFile(Point(kSFTopLeft),                {location}
  2021.               msg,                                {prompt String}
  2022.               'Doug',                            {original name}
  2023.               NIL,                                {dialog hook}
  2024.               reply);                            {record for returned values}
  2025.     IF reply.good THEN
  2026.         ShowSelection(reply.fName, '')
  2027.     ELSE
  2028.         ShowCancelled;
  2029. END;
  2030.  
  2031.  
  2032. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2033. {SFPPutFile with a Dialog Hook}
  2034. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2035.  
  2036. {This demonstration is a custom PutFile routine which adds a few items to
  2037. the dialog box.  There are two radio buttons that determine what format
  2038. the user wants to save the information in.  And there is a Static Text
  2039. item that displays the amount of free space left on the disk.  This text
  2040. item is updated whenever we detect that the user has switched to another
  2041. volume.
  2042.  
  2043. DoNormalPPut
  2044. ------------
  2045. This routine calls SFPPutFile.  Note the extra 'P' in the name.  This
  2046. Toolbox call allows us to specify a customized dialog box, and dialog
  2047. hook that handles hits on special items.  There is a global string used
  2048. to show what the file format will be to the user.
  2049.  
  2050. MySFPutDlgHook
  2051. --------------
  2052. A routine that is called to handle the non-standard items in our dialog
  2053. box.  These items are:
  2054.  
  2055. * The control of two radio buttons that affect the setting of a global
  2056.   variable used to determine what format the file will be saved in
  2057. * UserItem that displays the amount of free space on the current volume.
  2058.  
  2059. The dialog hook is also used to perform some special initialization on
  2060. the items in the dialog box.  We initialize our radio buttons, change the
  2061. text in the save button, and prepare the user item by initializing the
  2062. routine that will draw it and getting the text that will appear in it.
  2063.  
  2064. Finally, this routine will force the user item to be drawn once at
  2065. initialization time, and when ever the user clicks the Drive button.  By
  2066. invalidating the user item rectangle, it causes the drawing procedure to
  2067. update according to the current volume which is set by Standard File.
  2068.  
  2069. DrawFreeSpace
  2070. -------------
  2071. This is a user item's drawing procedure.  It is used to display the
  2072. amount of free space left on the disk.  All we need to do is update the
  2073. text in our userItem.}
  2074.  
  2075. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2076. {$S Main}
  2077.  
  2078. PROCEDURE DrawFreeSpace(theWindow: WindowPtr; itemNo: INTEGER);
  2079.  
  2080. VAR
  2081.     convert:            TwoIntsMakesALong;
  2082.     myStr1, myStr2:        Str255;
  2083.     iHandle:            Handle;
  2084.     iKind:                INTEGER;
  2085.     iRect:                Rect;
  2086.     err:                OSErr;
  2087.     space:                LONGINT;
  2088.  
  2089. BEGIN
  2090.     space:= GetKFreeSpace(GetSFVRefNum);
  2091.     IF space < 0 THEN
  2092.         myStr1:= '??'                            {we couldn't get the free space size!}
  2093.     ELSE
  2094.         NumToString(space, myStr1);
  2095.     GetIndString(myStr2, rStrMisc, sKFree);        {Text to be added in our user item.}
  2096.     myStr1:= CONCAT(myStr1, myStr2);
  2097.     GetDialogItem(DialogPtr(theWindow), kSFPPutFreeSpace, iKind, iHandle, iRect);
  2098.     TETextBox(@myStr1[1], LENGTH(myStr1), iRect, teJustCenter);
  2099. END;
  2100.  
  2101.  
  2102. FUNCTION MySFPutDlgHook(item: INTEGER; dlgPtr: DialogPtr): INTEGER;
  2103.  
  2104.  
  2105. VAR
  2106.     saveStr:            Str255;                 {replacement name for Save button}
  2107.     iRect:                Rect;
  2108.     iHandle:            Handle;
  2109.     iKind:                INTEGER;
  2110.  
  2111. BEGIN
  2112.     MySFPutDlgHook:= item;
  2113.     CASE item OF
  2114.  
  2115.         kFirstTime: BEGIN
  2116.             SetRadioButton(dlgPtr, kSFPPutTextBtn, kCntlOff);
  2117.             GetDialogItem(dlgPtr, kSFPPutNormalBtn, iKind, iHandle, iRect);
  2118.             SetControlValue(ControlHandle(iHandle), kCntlOn);
  2119.             GetControlTitle(ControlHandle(iHandle), gPutFormat);
  2120.             GetDialogItem(dlgPtr, kSFPPutFreeSpace, iKind, iHandle, iRect);
  2121.             SetDialogItem(dlgPtr, kSFPPutFreeSpace, iKind, Handle(@DrawFreeSpace), iRect);
  2122.             InvalRect(iRect);                    {force item to update}
  2123.         END;
  2124.  
  2125.         getDrive: BEGIN
  2126.             GetDialogItem(dlgPtr, kSFPPutFreeSpace, iKind, iHandle, iRect);
  2127.             InvalRect(iRect);
  2128.         END;
  2129.  
  2130.         kSFPPutNormalBtn: BEGIN
  2131.             IF NOT gNormal THEN BEGIN
  2132.                 SetRadioButton(dlgPtr, kSFPPutTextBtn, kCntlOff);
  2133.                 GetDialogItem(dlgPtr, kSFPPutNormalBtn, iKind, iHandle, iRect);
  2134.                 SetControlValue(ControlHandle(iHandle), kCntlOn);
  2135.                 GetControlTitle(ControlHandle(iHandle), gPutFormat);
  2136.                 gNormal:= TRUE;                    {change our flag accordingly}
  2137.             END;
  2138.         END;
  2139.  
  2140.         kSFPPutTextBtn: BEGIN
  2141.             IF gNormal THEN BEGIN
  2142.                 SetRadioButton(dlgPtr, kSFPPutNormalBtn, kCntlOff);
  2143.                 GetDialogItem(dlgPtr, kSFPPutTextBtn, iKind, iHandle, iRect);
  2144.                 SetControlValue(ControlHandle(iHandle), kCntlOn);
  2145.                 GetControlTitle(ControlHandle(iHandle), gPutFormat);
  2146.                 gNormal:= FALSE;                {change our flag accordingly}
  2147.             END;
  2148.         END;
  2149.  
  2150.     END; {CASE}
  2151. END;
  2152.  
  2153.  
  2154. PROCEDURE DoNormalPPut;
  2155.  
  2156. VAR
  2157.     msg:                Str255;                    {prompt String}
  2158.     reply:                SFReply;
  2159.     dhUPP:                DlgHookUPP;
  2160.  
  2161. BEGIN
  2162.     gNormal:= TRUE;
  2163.     GetIndString(msg, rStrMisc, sSaveAsMsg);
  2164.  
  2165.     dhUPP := NewDlgHookProc(@MySFPutDlgHook);
  2166.     SFPPutFile(Point(kSFTopLeft),                {location}
  2167.                msg,                             {prompt String}
  2168.                'Doug',                            {original name}
  2169.                dhUPP,                            {dialog hook}
  2170.                reply,                            {record for returned values}
  2171.                rSFPPutFileDLOG,                 {ID of custom Dialog}
  2172.                NIL);                            {ModalDialog filterProc}
  2173.     DisposeRoutineDescriptor(dhUPP);
  2174.  
  2175.     IF reply.good THEN
  2176.         ShowSelection(reply.fName, gPutFormat)
  2177.     ELSE
  2178.         ShowCancelled;
  2179. END;
  2180.  
  2181.  
  2182. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2183. {DoForceDirectory}
  2184. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2185.  
  2186. {This is a quick sample that shows how to set the initial directory that
  2187. Standard File comes up with.  Basically, this is done by storing
  2188. appropriate values into SFSaveDisk and CurDirStore.  In this example, I
  2189. force the directory to be the System Folder of the boot volume as
  2190. specified by SysEnvirons.  Since I like to be user friendly, I save and
  2191. restore the current folder.  So the next time Standard File is called it
  2192. will be exactly where the user left it.}
  2193.  
  2194. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2195. {$S Main}
  2196.  
  2197. PROCEDURE DoForceDirectory;
  2198.  
  2199. VAR
  2200.     msg:                Str255;                    {prompt String}
  2201.     typeList:            SFTypeList;
  2202.     oldDirStore:        LONGINT;
  2203.     dirID:                LONGINT;
  2204.     procID:                LONGINT;
  2205.     vRefNum:            INTEGER;
  2206.     oldVRefNum:            INTEGER;
  2207.     reply:                SFReply;
  2208.     err:                OSErr;
  2209.  
  2210. BEGIN
  2211.     oldDirStore:= GetSFCurDir;                    {save old folder and volume}
  2212.     oldVRefNum:= GetSFVRefNum;
  2213.     err:= GetWDInfo(gMac.sysVRefNum, vRefNum, dirID, procID);
  2214.     IF err = noErr THEN BEGIN
  2215.         SetSFCurDir(dirID);
  2216.         SetSFVRefNum(vRefNum);
  2217.         GetIndString(msg, rStrMisc, sSaveAsMsg);
  2218.         SFPutFile(Point(kSFTopLeft),            {location}
  2219.                   msg,                            {prompt String}
  2220.                   'Doug',                        {original name}
  2221.                   NIL,                            {dialog hook}
  2222.                   reply);                        {record for returned values}
  2223.         IF reply.good THEN
  2224.             ShowSelection(reply.fName, '')
  2225.         ELSE
  2226.             ShowCancelled;
  2227.         SetSFCurDir(oldDirStore);                 {restore old folder and volume}
  2228.         SetSFVRefNum(oldVRefNum);
  2229.     END
  2230.     ELSE
  2231.         AlertUser(err, sStandardErr);
  2232. END;
  2233.  
  2234.  
  2235. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2236. {DoPutListsFile}
  2237. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2238.  
  2239. {This sample demonstrates putting a List Manager list into an SFPPutFile
  2240. dialog, describes a problem that can occur by doing so, and shows one
  2241. solution to the problem.  Putting another list into a Standard File
  2242. dialog can be useful for a number of reasons.  While *I* can't think of a
  2243. reason I would want to put a second list into a PutFile dialog, it is
  2244. possible that you may wish to.  The best example that I could think of is
  2245. a list of file types for the user to choose.  However, there is a subtle
  2246. problem that arises if you do: in the following example, if were to
  2247. dispose of the list when the user clicked on Save, we may hit a snag.  It
  2248. is possible for the user to specify the name of a file that already
  2249. exists.  When that happens, s/he is presented with another dialog that
  2250. asks if they are sure of what they are doing.  At that point, the user
  2251. could press Cancel, and return us to the PutFile dialog, SANS our second
  2252. list! Neither can we defer disposing of list until Standard File is done
  2253. and returns to our application, as the Dialog box we were using is gone,
  2254. and the List Manager tries to perform an InvalRect on a non-existent
  2255. window when it erases its list and that will crash.
  2256.  
  2257. There are two solutions to this problem:
  2258.  
  2259. * Implement the list as a Custom Control.  Then, when it comes time to
  2260.   close the dialog box, the custom control will get called with a dispCntl
  2261.   message.  It can take that opportunity to call LDispose.  This technique
  2262.   is not presented here, as it is more appropriate for a Custom Control
  2263.   Sample program.  However, it is the suggested way to proceed.
  2264.  
  2265. * Ensure that the confirmation dialog box never comes up under Standard
  2266.   File's control.  By calling it ourself, we know what the user chooses.  If
  2267.   the user presses OK, we delete the offending file, dispose of our list,
  2268.   and return to Standard File.  If the user presses Cancel, we change the
  2269.   click on the Save button into a NULL event.  This is the algorithm we use
  2270.   below.
  2271.  
  2272. There is a major disadvantage with this last approach, however.  With it,
  2273. we cannot implement a safe saving procedure.  Normally, the best way to
  2274. save a file (disk space permitting), is to save the data to a temporary
  2275. file, delete the original, and then rename the temporary file to that of
  2276. the original.  However, with the second approach, the file is deleted well
  2277. before it is safe to do so.
  2278.  
  2279. This example *could* be reworked to avoid this problem.  For instance,
  2280. instead of being deleted, the offending file could be renamed to
  2281. something else or moved to another directory.
  2282.  
  2283. DoPutListsFile
  2284. --------------
  2285. We force the current folder of Standard File to be the location of the
  2286. temporary file that we've created.  This is to attempt to make the
  2287. Existing File? dialog show up.  Being a user friendly application, we
  2288. save and restore the user's current folder.  If the user did happen to
  2289. select to replace the file, we create a new one for the next time.
  2290.  
  2291. PutListsDlglFilter
  2292. ------------------
  2293. The Dialog Filter is used to handle clicks in the List Manager list.
  2294.  
  2295. PutListsDlgHook
  2296. ---------------
  2297. The initialization case is used to create a list of file types.  It also
  2298. jams in the name of the temporary file that we are using.  This is to try
  2299. and force the "Replace file?" dialog to show up.  This is only part of
  2300. the demonstration, not something that programs would really be doing.  If
  2301. the user attempts to save the file with the name of an existing file,
  2302. then the Existing File alert needs to be shown.  This is centered within
  2303. the main dialog alert properly according to the Human Interface Guidelines.
  2304.  
  2305. LNPFDrawList
  2306. ------------
  2307. Dialog Manager's user item drawing procedure to update the List Manager
  2308. list.}
  2309.  
  2310. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2311. {$S Main}
  2312.  
  2313. PROCEDURE LNPFDrawList(theWindow: WindowPtr; item: INTEGER);
  2314.  
  2315. BEGIN
  2316.     IF item = kListItem THEN BEGIN
  2317.         LUpdate(theWindow^.visRgn, gListHandle);
  2318.         DrawItemFrame(theWindow, kListItem);
  2319.     END;
  2320. END;
  2321.  
  2322.  
  2323. FUNCTION PutListsDlgHook(item: INTEGER; dlgPtr: DialogPtr): INTEGER;
  2324.  
  2325. VAR
  2326.     name:                Str63;
  2327.     fndrInfo:            FInfo;
  2328.     dLoc:                Point;
  2329.     iRect:                Rect;
  2330.     alertRect:            Rect;
  2331.     iHandle:            Handle;
  2332.     alertHandle:        AlertTHndl;
  2333.     parID:                LONGINT;
  2334.     vRefNum:             INTEGER;
  2335.     replaceItem:         INTEGER;
  2336.     iKind:                 INTEGER;
  2337.     choice:             INTEGER;
  2338.     err:                OSErr;
  2339.  
  2340. BEGIN
  2341.     replaceItem:= item;
  2342.     vRefNum:= GetSFVRefNum;
  2343.     parID:= GetSFCurDir;
  2344.     CASE replaceItem OF
  2345.  
  2346.         kFirstTime: BEGIN
  2347.             GetDialogItem(dlgPtr, kListItem, iKind, iHandle, iRect);
  2348.             CreateFPutList(dlgPtr, iRect, gListHandle, gPutFormat);
  2349.             SetDialogItem(dlgPtr, kListItem, iKind, Handle(@LNPFDrawList), iRect);
  2350.             name:= GetTempFileName;
  2351.             GetDialogItem(dlgPtr, putName, iKind, iHandle, iRect);
  2352.             SetDialogItemText(iHandle, name);
  2353.             TESetSelect(0, LENGTH(name), DialogPeek(dlgPtr)^.textH);
  2354.         END;
  2355.  
  2356.         putSave: BEGIN
  2357.             err:= HGetFInfo(vRefNum, parID, gReply.fName, fndrInfo);
  2358.             IF err = noErr THEN BEGIN            {if GetFInfo = noErr, then}
  2359.                                                 {this file already exists.}
  2360.  
  2361.                 alertHandle:= AlertTHndl(Get1Resource('ALRT', kExistingFileALRT));
  2362.                 HNoPurge(Handle(alertHandle));    {keep is around}
  2363.                 alertRect:= alertHandle^^.boundsRect;
  2364.                 GlobalToLocal(alertRect.topLeft);
  2365.                 GlobalToLocal(alertRect.botRight);
  2366.                 PositionTwoRects(dlgPtr^.portRect, alertRect, FixRatio(1,2), FixRatio(1,3));
  2367.                 LocalToGlobal(alertRect.topLeft);
  2368.                 LocalToGlobal(alertRect.botRight);
  2369.                 alertHandle^^.boundsRect:= alertRect;
  2370.                 ParamText(gReply.fName, '', '', '');
  2371.                 choice:= Alert(kExistingFileALRT, NIL);
  2372.                 HPurge(Handle(alertHandle));    {let it go}
  2373.                 IF choice = kReplaceItem THEN
  2374.                     err:= HDelete(vRefNum, parID, gReply.fName)
  2375.                 ELSE                            {Change "Save" into null event}
  2376.                     replaceItem:= kNullModalEvt;
  2377.             END;
  2378.         END;
  2379.  
  2380.     END; {CASE}
  2381.     IF (replaceItem = putSave) | (replaceItem = putCancel) THEN
  2382.         DisposeFPutList(gListHandle, gPutFormat);
  2383.     PutListsDlgHook:= replaceItem;
  2384. END;
  2385.  
  2386.  
  2387. FUNCTION PutListsDlglFilter(dlgPtr: DialogPtr; VAR evt: EventRecord;
  2388.                                  VAR itemHit: INTEGER): BOOLEAN;
  2389.  
  2390. VAR
  2391.     localPt:            Point;
  2392.     ignore:                BOOLEAN;
  2393.  
  2394. BEGIN
  2395.     IF evt.what = mouseDown THEN BEGIN
  2396.         localPt:= evt.where;
  2397.         GlobalToLocal(localPt);
  2398.         ignore:= LClick(localPt, evt.modifiers, gListHandle);
  2399.     END;
  2400.     PutListsDlglFilter:= FALSE;
  2401. END;
  2402.  
  2403.  
  2404. PROCEDURE DoPutListsFile;
  2405.  
  2406. VAR
  2407.     msg:                Str255;                    {prompt String}
  2408.     fName:                Str63;
  2409.     oldDirStore:        LONGINT;
  2410.     oldVRefNum:            INTEGER;
  2411.     err:                INTEGER;
  2412.     dhUPP:                DlgHookUPP;
  2413.     mfUPP:                ModalFilterUPP;
  2414.  
  2415. BEGIN
  2416.     oldDirStore:= GetSFCurDir;                    {save old folder and volume}
  2417.     oldVRefNum:= GetSFVRefNum;
  2418.     SetSFCurDir(gAppParID);                        {force to our app folder}
  2419.     SetSFVRefNum(gAppVRefNum);
  2420.  
  2421.     GetIndString(msg, rStrMisc, sSaveAsMsg);
  2422.  
  2423.     dhUPP := NewDlgHookProc(@PutListsDlgHook);
  2424.     mfUPP := NewModalFilterProc(@PutListsDlglFilter);
  2425.     SFPPutFile(Point(kSFTopLeft),                {location}
  2426.                msg,                             {prompt String}
  2427.                '',                                {original name}
  2428.                dhUPP,                            {dialog hook}
  2429.                gReply,                            {record for returned values}
  2430.                rPutListsFileDLOG,                {ID of custom Dialog}
  2431.                mfUPP);                            {ModalDialog filterProc}
  2432.     DisposeRoutineDescriptor(dhUPP);
  2433.     DisposeRoutineDescriptor(mfUPP);
  2434.  
  2435.     IF gReply.good THEN BEGIN
  2436.         ShowSelection(gReply.fName, gPutFormat);
  2437.         fName:= GetTempFileName;
  2438.         IF gReply.fName = fName THEN
  2439.             err:= HCreate(gAppVRefNum, gAppParID, fName, rAppSignature, 'TEMP');
  2440.     END
  2441.     ELSE
  2442.         ShowCancelled;
  2443.  
  2444.     SetSFCurDir(oldDirStore);                    {restore old folder and volume}
  2445.     SetSFVRefNum(oldVRefNum);
  2446. END;
  2447.  
  2448.  
  2449. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2450. {DoPutOptions with a Dialog Hook}
  2451. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2452.  
  2453. {This demonstration add a new button to the SFPutFile dialog.  This button
  2454. is used to bring up another dialog used to show a set of radio buttons.
  2455. The options dialog allows the user to select a file type, which will be
  2456. used to as the file format when saving the file.
  2457.  
  2458. DoPutOptions
  2459. ------------
  2460. Very simple version of creating a SFPPutFile dialog with a Dialog Hook
  2461. that checks for the addition button being selected.
  2462.  
  2463. PutOptionsDlgHook
  2464. -----------------
  2465. If the Options button is selected, then we bring up the options dialog.
  2466. The default button is outlined and we wait for the OK or Cancel item.
  2467. The new format is the item that was hit within the options dialog.  The
  2468. title of this item is displayed to the user in the SFPutFile dialog
  2469. showing the new file format.}
  2470.  
  2471. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2472. {$S Main}
  2473.  
  2474. FUNCTION PutOptionsDlgHook(item: INTEGER; dlgPtr: DialogPtr): INTEGER;
  2475.  
  2476. VAR
  2477.     title:                Str255;
  2478.     dlogTemplate:        DialogTHndl;
  2479.     subDlgPtr:            DialogPtr;
  2480.     dlogRect:            Rect;
  2481.     iRect:                Rect;
  2482.     iHandle:            Handle;
  2483.     itemHit:            INTEGER;
  2484.     iKind:                INTEGER;
  2485.     newItem:            INTEGER;
  2486.     oldPort:            GrafPtr;
  2487.  
  2488. BEGIN
  2489.     PutOptionsDlgHook:= item;
  2490.     CASE item OF
  2491.  
  2492.         kOptionsButton: BEGIN
  2493.             GetPort(oldPort);
  2494.  
  2495.             dlogTemplate := DialogTHndl(Get1Resource('DLOG', rOptionsSubDLOG));
  2496.             dlogRect:= dlogTemplate^^.boundsRect;
  2497.             GlobalToLocal(dlogRect.topLeft);
  2498.             GlobalToLocal(dlogRect.botRight);
  2499.             PositionTwoRects(dlgPtr^.portRect, dlogRect, FixRatio(1,2), FixRatio(1,3));
  2500.             LocalToGlobal(dlogRect.topLeft);
  2501.             LocalToGlobal(dlogRect.botRight);
  2502.             dlogTemplate^^.boundsRect:= dlogRect;
  2503.  
  2504.             subDlgPtr:= GetNewDialog(rOptionsSubDLOG, NIL, WindowPtr(-1));
  2505.             GetDialogItem(subDlgPtr, OK, iKind, iHandle, iRect);
  2506.             OutlineButton(ControlHandle(iHandle));
  2507.             SetRadioButton(subDlgPtr, kDefaultFormat, kCntlOn);
  2508.             GetDialogItem(subDlgPtr, kFrameItem, iKind, iHandle, iRect);
  2509.             SetDialogItem(subDlgPtr, kFrameItem, iKind, Handle(@DrawItemFrame), iRect);
  2510.             newItem:= kDefaultFormat;
  2511.             REPEAT
  2512.                 ModalDialog(NIL, itemHit);
  2513.                 IF (itemHit <> newItem) & (itemHit > cancel) THEN BEGIN
  2514.                     SetRadioButton(subDlgPtr, newItem, kCntlOff);
  2515.                     SetRadioButton(subDlgPtr, itemHit, kCntlOn);
  2516.                     newItem:= itemHit;
  2517.                 END;
  2518.             UNTIL (itemHit = OK) | (itemHit = Cancel);
  2519.  
  2520.             IF itemHit = OK THEN BEGIN
  2521.                 GetDialogItem(subDlgPtr, newItem, iKind, iHandle, iRect);
  2522.                 GetControlTitle(ControlHandle(iHandle), gPutFormat);
  2523.                 GetDialogItem(dlgPtr, kFormatString, iKind, iHandle, iRect);
  2524.                 SetDialogItemText(iHandle, gPutFormat);
  2525.             END;
  2526.             DisposeDialog(subDlgPtr);
  2527.             SetPort(oldPort);
  2528.         END;
  2529.  
  2530.     END; {CASE}
  2531. END;
  2532.  
  2533.  
  2534. PROCEDURE DoPutOptions;
  2535.  
  2536. VAR
  2537.     msg:                Str255;                    {prompt String}
  2538.     reply:                SFReply;
  2539.     dhUPP:                DlgHookUPP;
  2540.  
  2541. BEGIN
  2542.     GetIndString(msg, rStrMisc, sSaveAsMsg);
  2543.  
  2544.     dhUPP := NewDlgHookProc(@PutOptionsDlgHook);
  2545.     SFPPutFile(Point(kSFTopLeft),                {location}
  2546.                 msg,                             {prompt String}
  2547.                 'Doug',                            {original name}
  2548.                 dhUPP,                             {dialog hook}
  2549.                 reply,                            {record for returned values}
  2550.                 rOptionsDLOG,                    {ID of custom Dialog}
  2551.                 NIL);                            {ModalDialog filterProc}
  2552.     DisposeRoutineDescriptor(dhUPP);
  2553.  
  2554.     IF reply.good THEN
  2555.         ShowSelection(reply.fName, gPutFormat)
  2556.     ELSE
  2557.         ShowCancelled;
  2558. END;
  2559.  
  2560. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2561. {DoIdleUpdates}
  2562. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2563.  
  2564. {This demonstrates two things: how to handle idle events and how to
  2565. determine the currently selected file.  There is a problem that Standard
  2566. File has when updates are pending in background windows of the current
  2567. application partition.  Standard File calls ModalDialog at the heart of
  2568. its main loop.  ModalDialog calls GetNextEvent, and then calls a
  2569. filterProc internal to Standard File.  This filterProc performs some
  2570. processing on the event, (like handling hits on the filename list, the
  2571. Current Directory button, and the Disk Name Icon), and then calls the
  2572. user filterProc specified in SFPPutFile or SFPGetFile calls.  Another one
  2573. of the things that the internal filterProc does is look for a nullEvent.
  2574. When one is found, the filterProc returns 100 as the "itemHit".  This
  2575. forces ModalDialog to return to Standard File, which in turn can now call
  2576. the Dialog Hook with the manufactured item number 100, indicating that
  2577. idle time processing can be performed.
  2578.  
  2579. The problem occurs when there are updates pending in windows open in the
  2580. current application's partition.  These updates will 'clog' the event
  2581. queue.  When ModalDialog calls GetNextEvent, it will get the update event.
  2582. However, there is no way for ModalDialog to respond to them.  It therefore
  2583. passes the event off to the filterProc, which is Standard File's internal
  2584. one.  This filterProc doesn't know how to handle it either.  Normally, the
  2585. event would be ignored at this point, and the update event would be
  2586. unresolved.  Control returns back to ModalDialog, which calls GetNextEvent
  2587. again, and gets the another update event since it wasn't handled.  Null
  2588. events will not get returned by GetNextEvent and, hence, the dialog hook
  2589. will never get called with itemHit=100.
  2590.  
  2591. This situation can be solved by providing your own filterProc procedure
  2592. to be called after Standard File's's internal Dialog Filter.  It will be
  2593. this routine's responsibility to check for update events, and handle them
  2594. appropriately.  Usually, this would mean that the update procedure within
  2595. the application would be called, and the update could be cleared by
  2596. BeginUpdate/EndUpdate.  However, the filterProc could also just handle
  2597. update events in the same way that Standard File's filterProc handles
  2598. null events.  This is done by returning 100 in the ItemHit parameter and
  2599. TRUE as the function result.  This is the approach taken by the sample
  2600. below.
  2601.  
  2602. DoIdleUpdates
  2603. -------------
  2604. Create a window with a non-empty update region then call up SFPGetFile
  2605. with a Dialog Hook and Dialog Filter.
  2606.  
  2607. IdleDlgFilter
  2608. -------------
  2609. If we get an update event for a window other than the dialog box, change
  2610. it to a null event, and tell ModalDialog that we handled it.  This allows
  2611. our Dialog Hook idle time.
  2612.  
  2613. IdleDlgHook
  2614. -----------
  2615. All it does is wait around for null events and draws the current time
  2616. when it gets one.  Note that this routine will NOT get called with
  2617. item=100 if we did not have the IdleDlgFilter below.  Determining if
  2618. there is an item selected is straight forward, once you know the secret.
  2619. During the idle event the SFReply record will contain the file name if a
  2620. file is selected.  (It's also worth noting here that if the fName field
  2621. is NIL, this could mean a folder is selected.  If this were the case then
  2622. fType will be the DirID of that folder!  Pretty cool huh?)
  2623.  
  2624. Given that we can determine the name of the file, it's easy to find out
  2625. the size.  So, just for fun we'll show the file's size on the disk to the
  2626. user.  ioFlPyLen is the physical size of the file on the device.  Logical
  2627. size would be the actual size of the file in memory.  In other words,
  2628. GetEOF returns the logical size.  The data and resource fork's physical
  2629. sizes are added together to give the true size of the entire file on the
  2630. volume.}
  2631.  
  2632.  
  2633. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2634. {$S Main}
  2635.  
  2636. FUNCTION IdleDlgHook(item: INTEGER; dlgPtr: DialogPtr): INTEGER;
  2637.  
  2638. VAR
  2639.     newStr:             Str255;
  2640.     itemStr:            Str255;
  2641.     conCatStr:            Str255;
  2642.     hPBRec:                HParamBlockRec;
  2643.     dateTime:            LONGINT;
  2644.     totalSize:            LONGINT;                {size of file in k bytes}
  2645.     iRect:                Rect;
  2646.     iHandle:            Handle;
  2647.     iKind:                INTEGER;
  2648.     err:                OSErr;
  2649.  
  2650. BEGIN
  2651.     IdleDlgHook:= item;
  2652.     IF item = kNullModalEvt THEN BEGIN
  2653.         GetDateTime(dateTime);
  2654.         IUTimeString(dateTime, kWantSeconds, newStr);
  2655.         GetDialogItem(dlgPtr, kTimeItem, iKind, iHandle, iRect);
  2656.         GetDialogItemText(iHandle, itemStr);
  2657.         IF newStr <> itemStr THEN
  2658.             SetDialogItemText(iHandle, newStr);
  2659.  
  2660.         GetDialogItem(dlgPtr, kSizeItem, iKind, iHandle, iRect);
  2661.         GetDialogItemText(iHandle, itemStr);
  2662.         IF gReply.fName <> '' THEN BEGIN
  2663.             WITH hPBRec DO BEGIN
  2664.                 ioCompletion:= NIL;
  2665.                 ioNamePtr:= @gReply.fName;
  2666.                 ioVRefNum:= GetSFVRefNum;
  2667.                 ioFDirIndex:= 0;
  2668.                 ioDirID:= GetSFCurDir;
  2669.             END;
  2670.             err:= PBHGetFInfo(@hPBRec, NOT kFSAsynch);
  2671.             IF err = noErr THEN BEGIN
  2672.                 newStr:= gReply.fName;
  2673.                 newStr[0]:= CHR(INTEGER(newStr[0]) + 1);
  2674.                 newStr[LENGTH(newStr)]:= ' ';
  2675.  
  2676.                 totalSize:= (hPBRec.ioFlPyLen + hPBRec.ioFlRPyLen) DIV 1024;
  2677.                 NumToString(totalSize, conCatStr);
  2678.                 newStr:= CONCAT(newStr, conCatStr);
  2679.  
  2680.                 GetIndString(conCatStr, rStrMisc, sFileSize);
  2681.                 newStr:= CONCAT(newStr, conCatStr);
  2682.  
  2683.                 IF newStr <> itemStr THEN
  2684.                     SetDialogItemText(iHandle, newStr);
  2685.             END
  2686.             ELSE
  2687.                 SetDialogItemText(iHandle, '??');
  2688.         END
  2689.         ELSE
  2690.             SetDialogItemText(iHandle, '');
  2691.     END;
  2692. END;
  2693.  
  2694.  
  2695. FUNCTION IdleDlgFilter(dlgPtr: DialogPtr; VAR evt: EventRecord;
  2696.                         VAR itemHit: INTEGER): BOOLEAN;
  2697.  
  2698. BEGIN
  2699.     IdleDlgFilter:= FALSE;
  2700.     IF (evt.what = updateEvt) & (DialogPtr(evt.message) <> dlgPtr) THEN BEGIN
  2701.         itemHit:= kNullModalEvt;
  2702.         IdleDlgFilter:= TRUE;
  2703.     END;
  2704. END;
  2705.  
  2706.  
  2707. PROCEDURE DoIdleUpdates;
  2708.  
  2709. VAR
  2710.     typeList:            SFTypeList;
  2711.     window:                WindowPtr;
  2712.     dhUPP:                DlgHookUPP;
  2713.     mfUPP:                ModalFilterUPP;
  2714.  
  2715. BEGIN
  2716.     window:= GetNewWindow(rUpdateWindow, NIL, Pointer(-1));
  2717.  
  2718.     dhUPP := NewDlgHookProc(@IdleDlgHook);
  2719.     mfUPP := NewModalFilterProc(@IdleDlgFilter);
  2720.     SFPGetFile(Point(kSFTopLeft),                {location}
  2721.                '',                                {vestigial String}
  2722.                NIL,                             {file filter}
  2723.                kShowAllFiles,                    {numtypes}
  2724.                typeList,                        {array to types to show}
  2725.                dhUPP,                            {Dialog Hook}
  2726.                gReply,                            {record for returned values}
  2727.                rGetIdleUpdates,                    {ID of Normal Dialog}
  2728.                mfUPP);                            {ModalDialog filterProc}
  2729.     DisposeRoutineDescriptor(dhUPP);
  2730.     DisposeRoutineDescriptor(mfUPP);
  2731.  
  2732.     CloseWindow(window);
  2733.     IF gReply.good THEN
  2734.         ShowSelection(gReply.fName, '')
  2735.     ELSE
  2736.         ShowCancelled;
  2737. END;
  2738.  
  2739.  
  2740. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2741. {DoRememberFile}
  2742. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2743.  
  2744. {This demonstrates how to ask the user to locate a file and then how the
  2745. application should remember it.  The proper way to do this is use the
  2746. volume name, directory ID and file name.  Do not use the vRefNum.  It
  2747. could be a working directory which will change, probably as soon as your
  2748. application quits.  It could also be a real vRefNum but that could change
  2749. as soon as the user re-boots.  Directory ID do not change unless the user
  2750. deletes the folder.  No matter where the user places that folder or if
  2751. they rename it, the DirID will be the same.
  2752.  
  2753. Real applications should have a preference file.  This would store the
  2754. users options, such as the location of a file.  For the purposes of this
  2755. demonstration, we're using the application's resource fork to hold the
  2756. file's location.  Some potential problem with the method is if the file
  2757. is locked, the volume is lock, or the application is on a shared volume
  2758. by many different users.
  2759.  
  2760. RecallFile
  2761. ----------
  2762. Get the application defined resource and see if the file it describes can
  2763. be used to locate a file.  Start by changing the volume name into a
  2764. vRefNum.  If this works, then a volume with that name is mounted.  Then
  2765. we attempt to show a dialog that displays some information to the user
  2766. proving we've located their file.  If we could not locate the file, then
  2767. we ask them to locate another one and we'll try again next time.
  2768.  
  2769. DoRememberFile
  2770. --------------
  2771. Call upon SFGetFile to ask the user to select a file.  Then change the
  2772. working directory number returned in vRefNum into a real vRefNum and
  2773. DirID.  The real vRefNum is then used to option the volume name.  This
  2774. information along with the file name is saved into an application defined
  2775. resource and saved.}
  2776.  
  2777. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2778. {$S Main}
  2779.  
  2780. PROCEDURE DoRememberFile;
  2781.  
  2782. VAR
  2783.     volName:            Str27;
  2784.     typeList:            SFTypeList;
  2785.     reply:                SFReply;
  2786.     myWDPBRec:            WDPBRec;
  2787.     rememberFile:        FileDescHdl;
  2788.     err:                OSErr;
  2789.  
  2790. BEGIN
  2791.     SFGetFile(Point(kSFTopLeft),                {location}
  2792.               '',                                {prompt String}
  2793.               NIL,                                {file filter}
  2794.               kShowAllFiles,                    {numtypes}
  2795.               typeList,                         {array to types to show}
  2796.               NIL,                                {dialog hook}
  2797.               reply);                            {record for returned values}
  2798.     IF reply.good THEN BEGIN
  2799.         ShowSelection(reply.fName, '');
  2800.         volName:= '';                            {best to empty volName}
  2801.         WITH myWDPBRec DO BEGIN                    {set up WDPBRec}
  2802.             ioNamePtr:= @volName;
  2803.             ioVRefNum:= reply.vRefnum;
  2804.             ioWDIndex:= 0;
  2805.             ioWDProcID:= 0;
  2806.         END;
  2807.         err:= PBGetWDInfo(@myWDPBRec, NOT kFSAsynch);
  2808.         IF err = noErr THEN BEGIN
  2809.             rememberFile:= FileDescHdl(Get1Resource('FILE', rMemorizedFile));
  2810.             IF rememberFile <> NIL THEN BEGIN
  2811.                 rememberFile^^.name:= reply.fName;
  2812.                 rememberFile^^.parID:= myWDPBRec.ioWDDirID;
  2813.                 rememberFile^^.volName:= volName;
  2814.                 ChangedResource(Handle(rememberFile));
  2815.                 WriteResource(Handle(rememberFile));
  2816.             END
  2817.             ELSE
  2818.                 AlertUser(ResError, sResErr);
  2819.         END
  2820.         ELSE
  2821.             AlertUser(err, sFileSystem);
  2822.     END
  2823.     ELSE
  2824.         ShowCancelled;
  2825. END;
  2826.  
  2827.  
  2828. PROCEDURE RecallFile;
  2829.  
  2830. VAR
  2831.     rememberFile:        FileDescHdl;
  2832.     vRefNum:            INTEGER;
  2833.     err:                OSErr;
  2834.  
  2835. BEGIN
  2836.     rememberFile:= FileDescHdl(Get1Resource('FILE', rMemorizedFile));
  2837.     IF rememberFile <> NIL THEN BEGIN
  2838.         HLock(Handle(rememberFile));
  2839.         WITH rememberFile^^ DO BEGIN
  2840.             err:= GetVol(@volName, vRefNum);
  2841.             IF err = noErr THEN
  2842.                 err:= ShowFoundFile(name, parID, vRefNum);
  2843.         END;
  2844.         HUnlock(Handle(rememberFile));
  2845.         IF err <> noErr THEN BEGIN                {catch any file errors}
  2846.             AlertUser(noErr, sLostFile);
  2847.             DoRememberFile;
  2848.         END;
  2849.     END
  2850.     ELSE
  2851.         AlertUser(ResError, sResErr);
  2852. END;
  2853.  
  2854. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2855. {$S Main}
  2856.  
  2857. PROCEDURE DoCommand(mResult: LONGINT);
  2858.  
  2859. {Performed the selected menu command}
  2860.  
  2861. VAR
  2862.     name:                Str255;
  2863.     oldPort:            GrafPtr;
  2864.     theItem:            INTEGER;
  2865.     theMenu:            INTEGER;
  2866.     daRefNum:            INTEGER;
  2867.     ignore:             BOOLEAN;
  2868.  
  2869. BEGIN
  2870.     theItem:= LoWord(mResult);
  2871.     theMenu:= HiWord(mResult);
  2872.  
  2873.     CASE theMenu OF
  2874.  
  2875.         mApple:
  2876.             IF theItem = iAboutMe THEN
  2877.                 DoAbout
  2878.             ELSE BEGIN
  2879.                 GetMenuItemText(GetMenuHandle(mApple), theItem, name);
  2880.                 GetPort(oldPort);
  2881.                 daRefNum:= OpenDeskAcc(name);
  2882.                 SetPort(oldPort);
  2883.             END;
  2884.  
  2885.         mFile:
  2886.             CASE theItem OF
  2887.                 iQuit:
  2888.                     Terminate;
  2889.             END;
  2890.  
  2891.         mEdit:
  2892.             ignore:= SystemEdit(theItem - 1);
  2893.  
  2894.         mSFExamples: BEGIN
  2895.             CASE theItem OF
  2896.                 iNormalGet:
  2897.                     ShowPath;
  2898.                 iNormalPGet:
  2899.                     DoNormalPGet;
  2900.                 iFileFilter:
  2901.                     DoFFilter;
  2902.                 iGetDirectory:
  2903.                     DoGetDirectory;
  2904.                 iMultiFile:
  2905.                     DoMultiFile;
  2906.                 iNormalPut:
  2907.                     DoNormalPut;
  2908.                 iNormalPPut:
  2909.                     DoNormalPPut;
  2910.                 iForceDirectory:
  2911.                     DoForceDirectory;
  2912.                 iPutListsFile:
  2913.                     DoPutListsFile;
  2914.                 iPutOptions:
  2915.                     DoPutOptions;
  2916.                 iIdleUpdates:
  2917.                     DoIdleUpdates;
  2918.                 iRememberFile:
  2919.                     DoRememberFile;
  2920.             END; {CASE}
  2921.         END;
  2922.     END;
  2923.     HiliteMenu(0);                                {unhighlight all menus}
  2924. END;
  2925.  
  2926.  
  2927. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2928. {$S Main}
  2929.  
  2930. PROCEDURE EventLoop;
  2931.  
  2932. {Chances are, you've seen an event loop before so I'm not going to
  2933. comment about it.}
  2934.  
  2935. CONST
  2936.     osEvt                = app4Evt;                {defined in post MPW 3.0 Events.p}
  2937.     suspendResumeMessage = 1;                    {defined in post MPW 3.0 Events.p}
  2938.     kGetHighByte        = 24;                    {24 bits (three bytes) to shift right}
  2939.     kResumeMask         = 1;                    {resume event is low bit of message}
  2940.  
  2941. VAR
  2942.     evt:                EventRecord;
  2943.     whichWindow:        WindowPtr;                {used for FindWindow}
  2944.     deskPart:            INTEGER;                {result from FindWindow}
  2945.     err:                OSErr;
  2946.     haveEvent:            BOOLEAN;                {TRUE if interesting event}
  2947.  
  2948. BEGIN
  2949.     REPEAT
  2950.         IF gHasWaitNextEvent THEN
  2951.             haveEvent:= WaitNextEvent(everyEvent, evt, MAXLONGINT, NIL)
  2952.         ELSE BEGIN
  2953.             SystemTask;
  2954.             haveEvent:= GetNextEvent(everyEvent, evt);
  2955.         END;
  2956.  
  2957.         deskPart:= FindWindow(evt.where, whichWindow);
  2958.         IF deskPart <> inSysWindow THEN
  2959.             SetCursor(qd.arrow);
  2960.  
  2961.         IF haveEvent THEN BEGIN
  2962.             CASE evt.what OF
  2963.  
  2964.                 mouseDown:
  2965.                     CASE deskPart OF
  2966.                         inSysWindow:
  2967.                             SystemClick(evt, whichWindow);
  2968.                         inMenuBar:
  2969.                             DoCommand(MenuSelect(evt.where));
  2970.                     END;
  2971.  
  2972.                 keyDown, autoKey:
  2973.                     IF BAND(evt.modifiers, cmdKey) <> 0 THEN
  2974.                         DoCommand(MenuKey(CHR(BAND(evt.message, charCodeMask))));
  2975.  
  2976.                 diskEvt: {Call DIBadMount in response to a diskEvt}
  2977.                     IF HiWord(evt.message) <> noErr THEN
  2978.                         err:= DIBadMount(Point(kSFTopLeft), evt.message);
  2979.  
  2980.                 osEvt: BEGIN
  2981.                     CASE BSR(evt.message, kGetHighByte) OF
  2982.                         suspendResumeMessage: BEGIN
  2983.                             IF BAND(evt.message, kResumeMask) <> 0 THEN BEGIN
  2984.                                 gInBackground:= FALSE;
  2985.                             END
  2986.                             ELSE gInBackground:= TRUE; {suspend event}
  2987.                         END; {suspendResumeMessage}
  2988.                     END;
  2989.                 END;
  2990.  
  2991.             END; {CASE}
  2992.         END;
  2993.     UNTIL FALSE;                                {loop forever, quit through Terminate}
  2994. END;
  2995.  
  2996. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  2997. {$S Initialize}
  2998.  
  2999. PROCEDURE Initialize;
  3000.  
  3001. {Due to some bug in older versions of A/UX regarding working directories,
  3002. I check which verison we might be running if any.  If A/UX isn't running,
  3003. then the version will be zero.  Other wise it will be set to the major
  3004. version number, such as 2 for 2.0.  I want to make sure that enough
  3005. memory is free for my application to run.  It is possible that user may
  3006. have adjusted the SIZE resource to too small a setting or for some other
  3007. reason the application started up in a very small memory partition.  I
  3008. preform this check after initializing all the Toolbox and the basic
  3009. features of this application.  The pentultimate thing is to save the
  3010. default directory and volume that the application is launched from in two
  3011. globals and create a temporary file.  This file is only used for
  3012. demonstration purposes.  Finally, we check if the file we last remembered
  3013. can be found.}
  3014.  
  3015. VAR
  3016.     fName:                Str63;
  3017.     event:                EventRecord;
  3018.     menuBar:            Handle;
  3019.     apParam:            Handle;
  3020.     ignoreError:        OSErr;
  3021.     count:                 INTEGER;
  3022.     auxVersion:            INTEGER;
  3023.     appResRef:            INTEGER;
  3024.     err:                OSErr;
  3025.     ignoreResult:        Boolean;
  3026.  
  3027. BEGIN
  3028.     gInBackground:= FALSE;                        {we’ll be in the foreground soon}
  3029.     gHasWaitNextEvent := TrapExists(_WaitNextEvent);
  3030.  
  3031.     FOR count:= 1 TO kNumberOfMasters DO        {allocate master pointer blocks}
  3032.         MoreMasters;
  3033.     InitGraf(@qd.thePort);                            {init managers, yawn...}
  3034.     InitFonts;
  3035.     InitWindows;
  3036.     InitMenus;
  3037.     TEInit;
  3038.     InitDialogs(NIL);
  3039.     InitCursor;
  3040.  
  3041. {This code is necessary to pull the application into the foreground.  I use
  3042.  EventAvail because I don’t want to remove any events the user may have
  3043.  done, such as typing ahead.  Until the application has made a few calls (3
  3044.  seems to be the magic number) to the Event Manager, MultiFinder keeps me
  3045.  in the background.   Splashscreens and Alerts will remain in a background
  3046.  layer until we get a few events.  This is documented in Tech Note #180.}
  3047.  
  3048.     FOR count:= 1 TO kBroughtToFront DO
  3049.         ignoreResult:= EventAvail(everyEvent, event);
  3050.  
  3051.     auxVersion:= GetAUXVersion;
  3052.     IF (auxVersion < kMinAUXVersion) & (auxVersion > 0) THEN
  3053.         EmergencyExit(sOldAUX);
  3054.  
  3055.     ignoreError:= SysEnvirons(kSysEnvironsVersion, gMac);
  3056.     IF (gMac.machineType < envMachUnknown) THEN    {we use HFS}
  3057.         EmergencyExit(sNoHFS);
  3058.  
  3059.     menuBar := GetNewMBar(rMenuBar);            {read menus into menu bar}
  3060.     IF menuBar = NIL THEN
  3061.         EmergencyExit(sNoMenus);
  3062.     SetMenuBar(menuBar);                        {install menus}
  3063.     DisposeHandle(menuBar);
  3064.     AppendResMenu(GetMenuHandle(mApple), 'DRVR');     {add DA names to Apple menu}
  3065.     DrawMenuBar;
  3066.  
  3067.     IF FailLowMemory(kMinSpace) THEN
  3068.         EmergencyExit(sLowMemory);
  3069.  
  3070.     GetAppParms(gAppName, appResRef, apParam);
  3071.     err:= MyHGetVol(NIL, gAppVRefNum, gAppParID);
  3072.     err:= HCreate(gAppVRefNum, gAppParID, GetTempFileName, rAppSignature, 'TEMP');
  3073.  
  3074.     RecallFile;                                    {what was that file?}
  3075. END;
  3076.  
  3077.  
  3078. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  3079. PROCEDURE _DataInit; EXTERNAL;
  3080.  
  3081. {This routine is contained in the MPW runtime library.    It will be placed
  3082. into the code segment used to initialize the A5 globals.  This external
  3083. reference to it is done so that we can unload that segment, named %A5Init.}
  3084.  
  3085. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  3086. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  3087. {$S Main}
  3088.  
  3089. BEGIN
  3090.     UnLoadSeg(@_DataInit);                        {note: _DataInit must not be in Main!}
  3091.  
  3092.     {If you have stack requirements that differ from the default, then you
  3093.     could use SetApplLimit to increase StackSpace at this point, before
  3094.     calling MaxApplZone.}
  3095.  
  3096.     MaxApplZone;                                {expand the heap so code segments load at
  3097.                                                     the top}
  3098.     Initialize;                                 {initialize the program}
  3099.     UnLoadSeg(@Initialize);                     {note: Initialize must not be in Main!}
  3100.     EventLoop;                                    {call the main event loop}
  3101. END.
  3102. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ MAIN PROGRAM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  3103. {~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~}
  3104.